diff --git a/.gitattributes b/.gitattributes index e69de29bb2d..c139e44b4dc 100644 --- a/.gitattributes +++ b/.gitattributes @@ -0,0 +1,3 @@ +*.png filter=lfs diff=lfs merge=lfs -text +*.jpg filter=lfs diff=lfs merge=lfs -text +*.jpeg filter=lfs diff=lfs merge=lfs -text diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5be7688b06e..d06999db34c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,11 +6,6 @@ name: Build on: push: branches: ["main"] - paths: - - "autogen/**" - - "test/**" - - ".github/workflows/build.yml" - - "setup.py" pull_request: branches: ["main"] merge_group: @@ -21,7 +16,39 @@ concurrency: cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} permissions: {} jobs: + paths-filter: + runs-on: ubuntu-latest + outputs: + hasChanges: ${{ steps.filter.outputs.autogen == 'true' || steps.filter.outputs.test == 'true' || steps.filter.outputs.workflows == 'true' || steps.filter.outputs.setup == 'true' }} + steps: + - uses: actions/checkout@v4 + - uses: dorny/paths-filter@v2 + id: filter + with: + filters: | + autogen: + - "autogen/**" + test: + - "test/**" + workflows: + - ".github/workflows/**" + setup: + - "setup.py" + - name: autogen has changes + run: echo "autogen has changes" + if: steps.filter.outputs.autogen == 'true' + - name: test has changes + run: echo "test has changes" + if: steps.filter.outputs.test == 'true' + - name: workflows has changes + run: echo "workflows has changes" + if: steps.filter.outputs.workflows == 'true' + - name: setup has changes + run: echo "setup has changes" + if: steps.filter.outputs.setup == 'true' build: + needs: paths-filter + if: needs.paths-filter.outputs.hasChanges == 'true' runs-on: ${{ matrix.os }} env: AUTOGEN_USE_DOCKER: ${{ matrix.os != 'ubuntu-latest' && 'False' }} @@ -30,6 +57,11 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest] python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + exclude: + - os: macos-latest + python-version: "3.8" + - os: macos-latest + python-version: "3.9" steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} @@ -39,9 +71,9 @@ jobs: - name: Install packages and dependencies run: | python -m pip install --upgrade pip wheel - pip install -e . + pip install -e .[cosmosdb] python -c "import autogen" - pip install pytest mock + pip install pytest-cov>=5 mock - name: Install optional dependencies for code executors # code executors and udfs auto skip without deps, so only run for python 3.11 if: matrix.python-version == '3.11' @@ -57,20 +89,52 @@ jobs: - name: Test with pytest skipping openai tests if: matrix.python-version != '3.10' && matrix.os == 'ubuntu-latest' run: | - pytest test --skip-openai --durations=10 --durations-min=1.0 + pytest test --ignore=test/agentchat/contrib --skip-openai --durations=10 --durations-min=1.0 - name: Test with pytest skipping openai and docker tests if: matrix.python-version != '3.10' && matrix.os != 'ubuntu-latest' run: | - pytest test --skip-openai --skip-docker --durations=10 --durations-min=1.0 - - name: Coverage + pytest test --ignore=test/agentchat/contrib --skip-openai --skip-docker --durations=10 --durations-min=1.0 + - name: Coverage with Redis if: matrix.python-version == '3.10' run: | pip install -e .[test,redis,websockets] - coverage run -a -m pytest test --ignore=test/agentchat/contrib --skip-openai --durations=10 --durations-min=1.0 - coverage xml + pytest test --ignore=test/agentchat/contrib --skip-openai --durations=10 --durations-min=1.0 + - name: Test with Cosmos DB + run: | + pip install -e .[test,cosmosdb] + pytest test/cache/test_cosmos_db_cache.py --skip-openai --durations=10 --durations-min=1.0 - name: Upload coverage to Codecov if: matrix.python-version == '3.10' uses: codecov/codecov-action@v3 with: file: ./coverage.xml flags: unittests + build-check: + if: always() + runs-on: ubuntu-latest + needs: [build] + steps: + - name: Get Date + shell: bash + run: | + echo "date=$(date +'%m/%d/%Y %H:%M:%S')" >> "$GITHUB_ENV" + + - name: Run Type is ${{ github.event_name }} + if: ${{ github.event_name != 'schedule' && github.event_name != 'workflow_dispatch'}} + shell: bash + run: | + echo "run_type=${{ github.event_name }}" >> "$GITHUB_ENV" + + - name: Fail workflow if build failed + id: check_build_failed + if: contains(join(needs.*.result, ','), 'failure') + uses: actions/github-script@v6 + with: + script: core.setFailed('Build Failed!') + + - name: Fail workflow if build cancelled + id: check_build_cancelled + if: contains(join(needs.*.result, ','), 'cancelled') + uses: actions/github-script@v6 + with: + script: core.setFailed('Build Cancelled!') diff --git a/.github/workflows/contrib-openai.yml b/.github/workflows/contrib-openai.yml index 4eda8d93071..b1b3e35e478 100644 --- a/.github/workflows/contrib-openai.yml +++ b/.github/workflows/contrib-openai.yml @@ -5,14 +5,15 @@ name: OpenAI4ContribTests on: pull_request: - branches: ['main'] + branches: ["main"] paths: - - 'autogen/**' - - 'test/agentchat/contrib/**' - - '.github/workflows/contrib-openai.yml' - - 'setup.py' -permissions: {} - # actions: read + - "autogen/**" + - "test/agentchat/contrib/**" + - ".github/workflows/contrib-openai.yml" + - "setup.py" +permissions: + {} + # actions: read # checks: read # contents: read # deployments: read @@ -24,6 +25,21 @@ jobs: python-version: ["3.10"] runs-on: ${{ matrix.os }} environment: openai1 + services: + pgvector: + image: ankane/pgvector + env: + POSTGRES_DB: postgres + POSTGRES_USER: postgres + POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }} + POSTGRES_HOST_AUTH_METHOD: trust + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 steps: # checkout to pr branch - name: Checkout @@ -40,12 +56,48 @@ jobs: python -m pip install --upgrade pip wheel pip install -e . python -c "import autogen" - pip install coverage pytest-asyncio + pip install pytest-cov>=5 pytest-asyncio - name: Install packages for test when needed run: | pip install docker - pip install qdrant_client[fastembed] - pip install -e .[retrievechat] + pip install -e .[retrievechat,retrievechat-qdrant,retrievechat-pgvector] + - name: Coverage + env: + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + AZURE_OPENAI_API_KEY: ${{ secrets.AZURE_OPENAI_API_KEY }} + AZURE_OPENAI_API_BASE: ${{ secrets.AZURE_OPENAI_API_BASE }} + OAI_CONFIG_LIST: ${{ secrets.OAI_CONFIG_LIST }} + run: | + pytest test/agentchat/contrib/retrievechat/ test/agentchat/contrib/retrievechat + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + file: ./coverage.xml + flags: unittests + AgentEvalTest: + strategy: + matrix: + os: [ubuntu-latest] + python-version: ["3.10"] + runs-on: ${{ matrix.os }} + environment: openai1 + steps: + # checkout to pr branch + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Install packages and dependencies + run: | + docker --version + python -m pip install --upgrade pip wheel + pip install -e . + python -c "import autogen" + pip install pytest-cov>=5 pytest-asyncio - name: Coverage env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} @@ -53,8 +105,7 @@ jobs: AZURE_OPENAI_API_BASE: ${{ secrets.AZURE_OPENAI_API_BASE }} OAI_CONFIG_LIST: ${{ secrets.OAI_CONFIG_LIST }} run: | - coverage run -a -m pytest test/agentchat/contrib/test_retrievechat.py test/agentchat/contrib/test_qdrant_retrievechat.py - coverage xml + pytest test/agentchat/contrib/agent_eval/test_agent_eval.py - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 with: @@ -83,7 +134,7 @@ jobs: python -m pip install --upgrade pip wheel pip install -e . python -c "import autogen" - pip install coverage pytest-asyncio + pip install pytest-cov>=5 pytest-asyncio - name: Install packages for test when needed run: | pip install docker @@ -94,8 +145,7 @@ jobs: AZURE_OPENAI_API_BASE: ${{ secrets.AZURE_OPENAI_API_BASE }} OAI_CONFIG_LIST: ${{ secrets.OAI_CONFIG_LIST }} run: | - coverage run -a -m pytest test/agentchat/contrib/test_compressible_agent.py - coverage xml + pytest test/agentchat/contrib/test_compressible_agent.py - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 with: @@ -124,7 +174,7 @@ jobs: python -m pip install --upgrade pip wheel pip install -e . python -c "import autogen" - pip install coverage pytest-asyncio + pip install pytest-cov>=5 pytest-asyncio - name: Install packages for test when needed run: | pip install docker @@ -135,8 +185,7 @@ jobs: AZURE_OPENAI_API_BASE: ${{ secrets.AZURE_OPENAI_API_BASE }} OAI_CONFIG_LIST: ${{ secrets.OAI_CONFIG_LIST }} run: | - coverage run -a -m pytest test/agentchat/contrib/test_gpt_assistant.py - coverage xml + pytest test/agentchat/contrib/test_gpt_assistant.py - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 with: @@ -165,7 +214,7 @@ jobs: python -m pip install --upgrade pip wheel pip install -e .[teachable] python -c "import autogen" - pip install coverage pytest + pip install pytest-cov>=5 - name: Coverage env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} @@ -173,8 +222,7 @@ jobs: AZURE_OPENAI_API_BASE: ${{ secrets.AZURE_OPENAI_API_BASE }} OAI_CONFIG_LIST: ${{ secrets.OAI_CONFIG_LIST }} run: | - coverage run -a -m pytest test/agentchat/contrib/capabilities/test_teachable_agent.py - coverage xml + pytest test/agentchat/contrib/capabilities/test_teachable_agent.py - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 with: @@ -183,8 +231,8 @@ jobs: AgentBuilder: strategy: matrix: - os: [ ubuntu-latest ] - python-version: [ "3.11" ] + os: [ubuntu-latest] + python-version: ["3.11"] runs-on: ${{ matrix.os }} environment: openai1 steps: @@ -203,7 +251,7 @@ jobs: python -m pip install --upgrade pip wheel pip install -e . python -c "import autogen" - pip install coverage pytest-asyncio + pip install pytest-cov>=5 pytest-asyncio - name: Install packages for test when needed run: | pip install -e .[autobuild] @@ -214,8 +262,7 @@ jobs: AZURE_OPENAI_API_BASE: ${{ secrets.AZURE_OPENAI_API_BASE }} OAI_CONFIG_LIST: ${{ secrets.OAI_CONFIG_LIST }} run: | - coverage run -a -m pytest test/agentchat/contrib/test_agent_builder.py - coverage xml + pytest test/agentchat/contrib/test_agent_builder.py - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 with: @@ -244,7 +291,7 @@ jobs: python -m pip install --upgrade pip wheel pip install -e .[websurfer] python -c "import autogen" - pip install coverage pytest + pip install pytest-cov>=5 - name: Coverage env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} @@ -253,84 +300,119 @@ jobs: OAI_CONFIG_LIST: ${{ secrets.OAI_CONFIG_LIST }} BING_API_KEY: ${{ secrets.BING_API_KEY }} run: | - coverage run -a -m pytest test/agentchat/contrib/test_web_surfer.py - coverage xml + pytest test/agentchat/contrib/test_web_surfer.py - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 with: file: ./coverage.xml flags: unittests ContextHandling: - strategy: - matrix: - os: [ubuntu-latest] - python-version: ["3.11"] - runs-on: ${{ matrix.os }} - environment: openai1 - steps: - # checkout to pr branch - - name: Checkout - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha }} - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - name: Install packages and dependencies - run: | - docker --version - python -m pip install --upgrade pip wheel - pip install -e . - python -c "import autogen" - pip install coverage pytest - - name: Coverage - env: - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - AZURE_OPENAI_API_KEY: ${{ secrets.AZURE_OPENAI_API_KEY }} - AZURE_OPENAI_API_BASE: ${{ secrets.AZURE_OPENAI_API_BASE }} - OAI_CONFIG_LIST: ${{ secrets.OAI_CONFIG_LIST }} - BING_API_KEY: ${{ secrets.BING_API_KEY }} - run: | - coverage run -a -m pytest test/agentchat/contrib/capabilities/test_context_handling.py - coverage xml - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 - with: - file: ./coverage.xml - flags: unittests + strategy: + matrix: + os: [ubuntu-latest] + python-version: ["3.11"] + runs-on: ${{ matrix.os }} + environment: openai1 + steps: + # checkout to pr branch + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Install packages and dependencies + run: | + docker --version + python -m pip install --upgrade pip wheel + pip install -e . + python -c "import autogen" + pip install pytest-cov>=5 + - name: Coverage + env: + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + AZURE_OPENAI_API_KEY: ${{ secrets.AZURE_OPENAI_API_KEY }} + AZURE_OPENAI_API_BASE: ${{ secrets.AZURE_OPENAI_API_BASE }} + OAI_CONFIG_LIST: ${{ secrets.OAI_CONFIG_LIST }} + BING_API_KEY: ${{ secrets.BING_API_KEY }} + run: | + pytest test/agentchat/contrib/capabilities/test_context_handling.py + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + file: ./coverage.xml + flags: unittests ImageGen: - strategy: - matrix: - os: [ubuntu-latest] - python-version: ["3.12"] - runs-on: ${{ matrix.os }} - environment: openai1 - steps: - # checkout to pr branch - - name: Checkout - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha }} - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - name: Install packages and dependencies - run: | - docker --version - python -m pip install --upgrade pip wheel - pip install -e .[lmm] - python -c "import autogen" - pip install coverage pytest - - name: Coverage - env: - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - run: | - coverage run -a -m pytest test/agentchat/contrib/capabilities/test_image_generation_capability.py - coverage xml - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 - with: - file: ./coverage.xml - flags: unittests + strategy: + matrix: + os: [ubuntu-latest] + python-version: ["3.12"] + runs-on: ${{ matrix.os }} + environment: openai1 + steps: + # checkout to pr branch + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Install packages and dependencies + run: | + docker --version + python -m pip install --upgrade pip wheel + pip install -e .[lmm] + python -c "import autogen" + pip install pytest-cov>=5 + - name: Coverage + env: + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + run: | + pytest test/agentchat/contrib/capabilities/test_image_generation_capability.py + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + file: ./coverage.xml + flags: unittests + + AgentOptimizer: + strategy: + matrix: + os: [ubuntu-latest] + python-version: ["3.11"] + runs-on: ${{ matrix.os }} + environment: openai1 + steps: + # checkout to pr branch + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Install packages and dependencies + run: | + docker --version + python -m pip install --upgrade pip wheel + pip install -e . + python -c "import autogen" + pip install pytest-cov>=5 + - name: Coverage + env: + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + AZURE_OPENAI_API_KEY: ${{ secrets.AZURE_OPENAI_API_KEY }} + AZURE_OPENAI_API_BASE: ${{ secrets.AZURE_OPENAI_API_BASE }} + OAI_CONFIG_LIST: ${{ secrets.OAI_CONFIG_LIST }} + run: | + pytest test/agentchat/contrib/test_agent_optimizer.py + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + file: ./coverage.xml + flags: unittests diff --git a/.github/workflows/contrib-tests.yml b/.github/workflows/contrib-tests.yml index ced35dc115b..005abe4ef8e 100644 --- a/.github/workflows/contrib-tests.yml +++ b/.github/workflows/contrib-tests.yml @@ -27,8 +27,11 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, macos-latest, windows-2019] + os: [macos-latest, windows-2019] python-version: ["3.9", "3.10", "3.11"] + exclude: + - os: macos-latest + python-version: "3.9" steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} @@ -38,15 +41,11 @@ jobs: - name: Install packages and dependencies for all tests run: | python -m pip install --upgrade pip wheel - pip install pytest + pip install pytest-cov>=5 - name: Install qdrant_client when python-version is 3.10 if: matrix.python-version == '3.10' run: | - pip install qdrant_client[fastembed] - - name: Install unstructured when python-version is 3.9 and not windows - if: matrix.python-version == '3.9' && matrix.os != 'windows-2019' - run: | - pip install unstructured[all-docs] + pip install -e .[retrievechat-qdrant] - name: Install packages and dependencies for RetrieveChat run: | pip install -e .[retrievechat] @@ -56,14 +55,99 @@ jobs: if [[ ${{ matrix.os }} != ubuntu-latest ]]; then echo "AUTOGEN_USE_DOCKER=False" >> $GITHUB_ENV fi - - name: Test RetrieveChat + - name: Coverage + run: | + pytest test/test_retrieve_utils.py test/agentchat/contrib/retrievechat/test_retrievechat.py test/agentchat/contrib/retrievechat/test_qdrant_retrievechat.py test/agentchat/contrib/vectordb --skip-openai + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + file: ./coverage.xml + flags: unittests + + RetrieveChatTest-Ubuntu: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.9", "3.10", "3.11"] + services: + pgvector: + image: ankane/pgvector + env: + POSTGRES_DB: postgres + POSTGRES_USER: postgres + POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }} + POSTGRES_HOST_AUTH_METHOD: trust + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Install packages and dependencies for all tests + run: | + python -m pip install --upgrade pip wheel + pip install pytest + - name: Install qdrant_client when python-version is 3.10 + if: matrix.python-version == '3.10' + run: | + pip install -e .[retrievechat-qdrant] + - name: Install pgvector when on linux run: | - pytest test/test_retrieve_utils.py test/agentchat/contrib/test_retrievechat.py test/agentchat/contrib/test_qdrant_retrievechat.py --skip-openai + pip install -e .[retrievechat-pgvector] + - name: Install unstructured when python-version is 3.9 and on linux + if: matrix.python-version == '3.9' + run: | + sudo apt-get update + sudo apt-get install -y tesseract-ocr poppler-utils + pip install unstructured[all-docs]==0.13.0 + - name: Install packages and dependencies for RetrieveChat + run: | + pip install -e .[retrievechat] + - name: Set AUTOGEN_USE_DOCKER based on OS + shell: bash + run: | + echo "AUTOGEN_USE_DOCKER=False" >> $GITHUB_ENV - name: Coverage run: | - pip install coverage>=5.3 - coverage run -a -m pytest test/test_retrieve_utils.py test/agentchat/contrib/test_retrievechat.py test/agentchat/contrib/test_qdrant_retrievechat.py --skip-openai - coverage xml + pip install pytest-cov>=5 + pytest test/test_retrieve_utils.py test/agentchat/contrib/retrievechat test/agentchat/contrib/vectordb --skip-openai + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + file: ./coverage.xml + flags: unittests + + AgentEvalTest: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest] + python-version: ["3.10"] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Install packages and dependencies for all tests + run: | + python -m pip install --upgrade pip wheel + pip install pytest-cov>=5 + - name: Install packages and dependencies for AgentEval + run: | + pip install -e . + - name: Coverage + run: | + pytest test/agentchat/contrib/agent_eval/ --skip-openai - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 with: @@ -76,7 +160,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-2019] - python-version: ["3.8"] + python-version: ["3.10"] steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} @@ -86,7 +170,7 @@ jobs: - name: Install packages and dependencies for all tests run: | python -m pip install --upgrade pip wheel - pip install pytest + pip install pytest-cov>=5 - name: Install packages and dependencies for Compression run: | pip install -e . @@ -98,9 +182,7 @@ jobs: fi - name: Coverage run: | - pip install coverage>=5.3 - coverage run -a -m pytest test/agentchat/contrib/test_compressible_agent.py --skip-openai - coverage xml + pytest test/agentchat/contrib/test_compressible_agent.py --skip-openai - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 with: @@ -123,7 +205,7 @@ jobs: - name: Install packages and dependencies for all tests run: | python -m pip install --upgrade pip wheel - pip install pytest + pip install pytest-cov>=5 - name: Install packages and dependencies for GPTAssistantAgent run: | pip install -e . @@ -135,9 +217,7 @@ jobs: fi - name: Coverage run: | - pip install coverage>=5.3 - coverage run -a -m pytest test/agentchat/contrib/test_gpt_assistant.py --skip-openai - coverage xml + pytest test/agentchat/contrib/test_gpt_assistant.py --skip-openai - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 with: @@ -160,7 +240,7 @@ jobs: - name: Install packages and dependencies for all tests run: | python -m pip install --upgrade pip wheel - pip install pytest + pip install pytest-cov>=5 - name: Install packages and dependencies for Teachability run: | pip install -e .[teachable] @@ -172,9 +252,7 @@ jobs: fi - name: Coverage run: | - pip install coverage>=5.3 - coverage run -a -m pytest test/agentchat/contrib/capabilities/test_teachable_agent.py --skip-openai - coverage xml + pytest test/agentchat/contrib/capabilities/test_teachable_agent.py --skip-openai - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 with: @@ -197,7 +275,7 @@ jobs: - name: Install packages and dependencies for all tests run: | python -m pip install --upgrade pip wheel - pip install pytest + pip install pytest-cov>=5 - name: Install packages and dependencies for WebSurfer run: | pip install -e .[websurfer] @@ -209,9 +287,7 @@ jobs: fi - name: Coverage run: | - pip install coverage>=5.3 - coverage run -a -m pytest test/test_browser_utils.py test/agentchat/contrib/test_web_surfer.py --skip-openai - coverage xml + pytest test/test_browser_utils.py test/agentchat/contrib/test_web_surfer.py --skip-openai - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 with: @@ -227,6 +303,8 @@ jobs: python-version: ["3.12"] steps: - uses: actions/checkout@v4 + with: + lfs: true - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: @@ -234,7 +312,7 @@ jobs: - name: Install packages and dependencies for all tests run: | python -m pip install --upgrade pip wheel - pip install pytest + pip install pytest-cov>=5 - name: Install packages and dependencies for LMM run: | pip install -e .[lmm] @@ -246,9 +324,51 @@ jobs: fi - name: Coverage run: | - pip install coverage>=5.3 - coverage run -a -m pytest test/agentchat/contrib/test_img_utils.py test/agentchat/contrib/test_lmm.py test/agentchat/contrib/test_llava.py test/agentchat/contrib/capabilities/test_image_generation_capability.py test/agentchat/contrib/capabilities/test_vision_capability.py --skip-openai - coverage xml + pytest test/agentchat/contrib/test_img_utils.py test/agentchat/contrib/test_lmm.py test/agentchat/contrib/test_llava.py test/agentchat/contrib/capabilities/test_vision_capability.py --skip-openai + - name: Image Gen Coverage + if: ${{ matrix.os != 'windows-2019' && matrix.python-version != '3.12' }} + run: | + pytest test/agentchat/contrib/capabilities/test_image_generation_capability.py --skip-openai + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + file: ./coverage.xml + flags: unittests + + GeminiTest: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-2019] + python-version: ["3.9", "3.10", "3.11", "3.12"] + exclude: + - os: macos-latest + python-version: "3.9" + steps: + - uses: actions/checkout@v4 + with: + lfs: true + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Install packages and dependencies for all tests + run: | + python -m pip install --upgrade pip wheel + pip install pytest-cov>=5 + - name: Install packages and dependencies for Gemini + run: | + pip install -e .[gemini,test] + - name: Set AUTOGEN_USE_DOCKER based on OS + shell: bash + run: | + if [[ ${{ matrix.os }} != ubuntu-latest ]]; then + echo "AUTOGEN_USE_DOCKER=False" >> $GITHUB_ENV + fi + - name: Coverage + run: | + pytest test/oai/test_gemini.py --skip-openai - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 with: @@ -271,7 +391,7 @@ jobs: - name: Install packages and dependencies for all tests run: | python -m pip install --upgrade pip wheel - pip install pytest + pip install pytest-cov>=5 - name: Install packages and dependencies for Context Handling run: | pip install -e . @@ -283,11 +403,44 @@ jobs: fi - name: Coverage run: | - pip install coverage>=5.3 - coverage run -a -m pytest test/agentchat/contrib/capabilities/test_context_handling.py --skip-openai - coverage xml + pytest test/agentchat/contrib/capabilities/test_context_handling.py --skip-openai - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 with: file: ./coverage.xml flags: unittests + + TransformMessages: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-2019] + python-version: ["3.11"] + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install packages and dependencies for all tests + run: | + python -m pip install --upgrade pip wheel + pip install pytest-cov>=5 + - name: Install packages and dependencies for Transform Messages + run: | + pip install -e '.[long-context]' + - name: Set AUTOGEN_USE_DOCKER based on OS + shell: bash + run: | + if [[ ${{ matrix.os }} != ubuntu-latest ]]; then + echo "AUTOGEN_USE_DOCKER=False" >> $GITHUB_ENV + fi + - name: Coverage + run: | + pytest test/agentchat/contrib/capabilities/test_transform_messages.py --skip-openai + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + file: ./coverage.xml + flags: unittest diff --git a/.github/workflows/dotnet-build.yml b/.github/workflows/dotnet-build.yml index d223fffd28b..2e679412f63 100644 --- a/.github/workflows/dotnet-build.yml +++ b/.github/workflows/dotnet-build.yml @@ -6,11 +6,11 @@ name: dotnet-ci on: workflow_dispatch: pull_request: - branches: [ "dotnet" ] - paths: - - 'dotnet/**' + branches: [ "main" ] push: - branches: [ "dotnet" ] + branches: [ "main" ] + merge_group: + types: [checks_requested] concurrency: group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref }} @@ -21,14 +21,38 @@ permissions: packages: write jobs: + paths-filter: + runs-on: ubuntu-latest + outputs: + hasChanges: ${{ steps.filter.outputs.dotnet == 'true'}} + steps: + - uses: actions/checkout@v4 + - uses: dorny/paths-filter@v2 + id: filter + with: + filters: | + dotnet: + - "dotnet/**" + workflows: + - ".github/workflows/**" + - name: dotnet has changes + run: echo "dotnet has changes" + if: steps.filter.outputs.dotnet == 'true' + - name: workflows has changes + run: echo "workflows has changes" + if: steps.filter.outputs.workflows == 'true' build: - name: Build + name: Dotnet Build runs-on: ubuntu-latest + needs: paths-filter + if: needs.paths-filter.outputs.hasChanges == 'true' defaults: run: working-directory: dotnet steps: - uses: actions/checkout@v4 + with: + lfs: true - name: Setup .NET uses: actions/setup-dotnet@v4 with: @@ -50,10 +74,12 @@ jobs: defaults: run: working-directory: dotnet - if: success() && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dotnet') + if: success() && (github.ref == 'refs/heads/main') needs: build steps: - uses: actions/checkout@v4 + with: + lfs: true - name: Setup .NET uses: actions/setup-dotnet@v4 with: @@ -139,4 +165,3 @@ jobs: dotnet nuget push --api-key ${{ secrets.MYGET_TOKEN }} --source "https://www.myget.org/F/agentchat/api/v3/index.json" ./output/nightly/*.nupkg --skip-duplicate env: MYGET_TOKEN: ${{ secrets.MYGET_TOKEN }} - diff --git a/.github/workflows/dotnet-release.yml b/.github/workflows/dotnet-release.yml index d66f21a6cd6..b512b4c1696 100644 --- a/.github/workflows/dotnet-release.yml +++ b/.github/workflows/dotnet-release.yml @@ -7,7 +7,7 @@ on: workflow_dispatch: push: branches: - - dotnet/release + - dotnet/release/** concurrency: group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref }} @@ -27,6 +27,8 @@ jobs: working-directory: dotnet steps: - uses: actions/checkout@v4 + with: + lfs: true - name: Setup .NET uses: actions/setup-dotnet@v4 with: @@ -57,13 +59,6 @@ jobs: echo "Publish package to Nuget" echo "ls output directory" ls -R ./output/release - dotnet nuget push --api-key AzureArtifacts ./output/release/*.nupkg --skip-duplicate --api-key ${{ secrets.AUTOGEN_NUGET_API_KEY }} - - name: Tag commit - run: | - Write-Host "Tag commit" - # version = eng/MetaInfo.props.Project.PropertyGroup.VersionPrefix - $metaInfoContent = cat ./eng/MetaInfo.props - $version = $metaInfoContent | Select-String -Pattern "(.*)" | ForEach-Object { $_.Matches.Groups[1].Value } - git tag -a "$version" -m "AutoGen.Net release $version" - git push origin --tags - shell: pwsh \ No newline at end of file + # remove AutoGen.SourceGenerator.snupkg because it's an empty package + rm ./output/release/AutoGen.SourceGenerator.*.snupkg + dotnet nuget push --api-key ${{ secrets.AUTOGEN_NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json ./output/release/*.nupkg --skip-duplicate diff --git a/.github/workflows/lfs-check.yml b/.github/workflows/lfs-check.yml new file mode 100644 index 00000000000..4baae925de3 --- /dev/null +++ b/.github/workflows/lfs-check.yml @@ -0,0 +1,15 @@ +name: "Git LFS Check" + +on: pull_request +permissions: {} +jobs: + lfs-check: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + lfs: true + - name: "Check Git LFS files for consistency, if you see error like 'pointer: unexpectedGitObject ... should have been a pointer but was not', please install Git LFS locally, delete the problematic file, and then add it back again. This ensures it's properly tracked." + run: | + git lfs fsck diff --git a/.github/workflows/openai.yml b/.github/workflows/openai.yml index d2780eea542..a9ab8e9e0c5 100644 --- a/.github/workflows/openai.yml +++ b/.github/workflows/openai.yml @@ -13,7 +13,8 @@ on: - "notebook/agentchat_function_call.ipynb" - "notebook/agentchat_groupchat_finite_state_machine.ipynb" - ".github/workflows/openai.yml" -permissions: {} +permissions: + {} # actions: read # checks: read # contents: read @@ -49,7 +50,7 @@ jobs: python -m pip install --upgrade pip wheel pip install -e. python -c "import autogen" - pip install coverage pytest-asyncio + pip install pytest-cov>=5 pytest-asyncio - name: Install packages for test when needed if: matrix.python-version == '3.9' run: | @@ -63,8 +64,7 @@ jobs: AZURE_OPENAI_API_BASE: ${{ secrets.AZURE_OPENAI_API_BASE }} OAI_CONFIG_LIST: ${{ secrets.OAI_CONFIG_LIST }} run: | - coverage run -a -m pytest test --ignore=test/agentchat/contrib --durations=10 --durations-min=1.0 - coverage xml + pytest test --ignore=test/agentchat/contrib --durations=10 --durations-min=1.0 - name: Coverage and check notebook outputs if: matrix.python-version != '3.9' env: @@ -75,8 +75,7 @@ jobs: OAI_CONFIG_LIST: ${{ secrets.OAI_CONFIG_LIST }} run: | pip install nbconvert nbformat ipykernel - coverage run -a -m pytest test/test_notebook.py --durations=10 --durations-min=1.0 - coverage xml + pytest test/test_notebook.py --durations=10 --durations-min=1.0 cat "$(pwd)/test/executed_openai_notebook_output.txt" - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 diff --git a/.github/workflows/samples-tools-tests.yml b/.github/workflows/samples-tools-tests.yml index 12c8de3b7af..e774e5cb0b1 100644 --- a/.github/workflows/samples-tools-tests.yml +++ b/.github/workflows/samples-tools-tests.yml @@ -24,6 +24,9 @@ jobs: matrix: os: [ubuntu-latest, macos-latest] python-version: ["3.9", "3.10", "3.11"] + exclude: + - os: macos-latest + python-version: "3.9" steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} @@ -34,7 +37,7 @@ jobs: run: | python -m pip install --upgrade pip wheel pip install -e . - pip install pytest + pip install pytest-cov>=5 - name: Set AUTOGEN_USE_DOCKER based on OS shell: bash run: | diff --git a/.github/workflows/type-check.yml b/.github/workflows/type-check.yml index f6896d1145d..c66fb6ad7b1 100644 --- a/.github/workflows/type-check.yml +++ b/.github/workflows/type-check.yml @@ -1,6 +1,6 @@ name: Type check # see: https://help.github.com/en/actions/reference/events-that-trigger-workflows -on: # Trigger the workflow on pull request or merge +on: # Trigger the workflow on pull request or merge pull_request: merge_group: types: [checks_requested] @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: - python-version: ${{ matrix.version }} + python-version: ${{ matrix.version }} # All additional modules should be defined in setup.py - run: pip install ".[types]" # Any additional configuration should be defined in pyproject.toml diff --git a/.gitignore b/.gitignore index 49a41e9ed2c..4c925f739ec 100644 --- a/.gitignore +++ b/.gitignore @@ -172,6 +172,10 @@ test/my_tmp/* # Storage for the AgentEval output test/test_files/agenteval-in-out/out/ +# local cache or coding foler +local_cache/ +coding/ + # Files created by tests *tmp_code_* test/agentchat/test_agent_scripts/* @@ -179,7 +183,10 @@ test/agentchat/test_agent_scripts/* # test cache .cache_test .db +local_cache notebook/result.png samples/apps/autogen-studio/autogenstudio/models/test/ + +notebook/coding diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 025cc7cbb17..fcea09223c6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -26,11 +26,12 @@ repos: rev: 24.3.0 hooks: - id: black - - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.3.3 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.3.4 hooks: - id: ruff - args: ["--fix"] + types_or: [ python, pyi, jupyter ] + args: ["--fix", "--ignore=E402"] - repo: https://github.com/codespell-project/codespell rev: v2.2.6 hooks: @@ -42,6 +43,8 @@ repos: website/static/img/ag.svg | website/yarn.lock | website/docs/tutorial/code-executors.ipynb | + website/docs/topics/code-execution/custom-executor.ipynb | + website/docs/topics/non-openai-models/cloud-gemini.ipynb | notebook/.* )$ # See https://jaredkhan.com/blog/mypy-pre-commit @@ -61,9 +64,6 @@ repos: # Print the number of files as a sanity-check verbose: true - repo: https://github.com/nbQA-dev/nbQA - rev: 1.8.4 + rev: 1.8.5 hooks: - - id: nbqa-ruff - # Don't require notebooks to have all imports at the top - args: ["--fix", "--ignore=E402"] - id: nbqa-black diff --git a/OAI_CONFIG_LIST_sample b/OAI_CONFIG_LIST_sample index ef027f815ba..9fc0dc803a0 100644 --- a/OAI_CONFIG_LIST_sample +++ b/OAI_CONFIG_LIST_sample @@ -5,7 +5,8 @@ [ { "model": "gpt-4", - "api_key": "" + "api_key": "", + "tags": ["gpt-4", "tool"] }, { "model": "", diff --git a/README.md b/README.md index 76f469ecef5..327dc8c4e54 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@ + + [![PyPI version](https://badge.fury.io/py/pyautogen.svg)](https://badge.fury.io/py/pyautogen) [![Build](https://github.com/microsoft/autogen/actions/workflows/python-package.yml/badge.svg)](https://github.com/microsoft/autogen/actions/workflows/python-package.yml) ![Python Version](https://img.shields.io/badge/3.8%20%7C%203.9%20%7C%203.10%20%7C%203.11%20%7C%203.12-blue) [![Downloads](https://static.pepy.tech/badge/pyautogen/week)](https://pepy.tech/project/pyautogen) -[![Discord](https://img.shields.io/discord/1153072414184452236?logo=discord&style=flat)](https://discord.gg/pAbnFJrkgZ) +[![Discord](https://img.shields.io/discord/1153072414184452236?logo=discord&style=flat)](https://aka.ms/autogen-dc) [![Twitter](https://img.shields.io/twitter/url/https/twitter.com/cloudposse.svg?style=social&label=Follow%20%40pyautogen)](https://twitter.com/pyautogen) @@ -12,29 +14,37 @@

--> -:fire: Mar 3: What's new in AutoGen? πŸ“°[Blog](https://microsoft.github.io/autogen/blog/2024/03/03/AutoGen-Update); πŸ“Ί[Youtube](https://www.youtube.com/watch?v=j_mtwQiaLGU). +:fire: May 13, 2024: [The Economist](https://www.economist.com/science-and-technology/2024/05/13/todays-ai-models-are-impressive-teams-of-them-will-be-formidable) published an article about multi-agent systems (MAS) following a January 2024 interview with [Chi Wang](https://github.com/sonichi). + +:fire: May 11, 2024: [AutoGen: Enabling Next-Gen LLM Applications via Multi-Agent Conversation](https://openreview.net/pdf?id=uAjxFFing2) received the best paper award in [ICLR 2024 LLM Agents Workshop](https://llmagents.github.io/). + +:fire: Apr 26, 2024: [AutoGen.NET](https://microsoft.github.io/autogen-for-net/) is available for .NET developers! -:fire: Mar 1: the first AutoGen multi-agent experiment on the challenging [GAIA](https://huggingface.co/spaces/gaia-benchmark/leaderboard) benchmark achieved the No. 1 accuracy in all the three levels. +:fire: Apr 17, 2024: Andrew Ng cited AutoGen in [The Batch newsletter](https://www.deeplearning.ai/the-batch/issue-245/) and [What's next for AI agentic workflows](https://youtu.be/sal78ACtGTc?si=JduUzN_1kDnMq0vF) at Sequoia Capital's AI Ascent (Mar 26). -:fire: Jan 30: AutoGen is highlighted by Peter Lee in Microsoft Research Forum [Keynote](https://t.co/nUBSjPDjqD). +:fire: Mar 3, 2024: What's new in AutoGen? πŸ“°[Blog](https://microsoft.github.io/autogen/blog/2024/03/03/AutoGen-Update); πŸ“Ί[Youtube](https://www.youtube.com/watch?v=j_mtwQiaLGU). -:fire: Dec 31: [AutoGen: Enabling Next-Gen LLM Applications via Multi-Agent Conversation Framework](https://arxiv.org/abs/2308.08155) is selected by [TheSequence: My Five Favorite AI Papers of 2023](https://thesequence.substack.com/p/my-five-favorite-ai-papers-of-2023). +:fire: Mar 1, 2024: the first AutoGen multi-agent experiment on the challenging [GAIA](https://huggingface.co/spaces/gaia-benchmark/leaderboard) benchmark achieved the No. 1 accuracy in all the three levels. + + + +:tada: Dec 31, 2023: [AutoGen: Enabling Next-Gen LLM Applications via Multi-Agent Conversation Framework](https://arxiv.org/abs/2308.08155) is selected by [TheSequence: My Five Favorite AI Papers of 2023](https://thesequence.substack.com/p/my-five-favorite-ai-papers-of-2023). -:fire: Nov 8: AutoGen is selected into [Open100: Top 100 Open Source achievements](https://www.benchcouncil.org/evaluation/opencs/annual.html) 35 days after spinoff. +:tada: Nov 8, 2023: AutoGen is selected into [Open100: Top 100 Open Source achievements](https://www.benchcouncil.org/evaluation/opencs/annual.html) 35 days after spinoff from [FLAML](https://github.com/microsoft/FLAML). -:fire: Nov 6: AutoGen is mentioned by Satya Nadella in a [fireside chat](https://youtu.be/0pLBvgYtv6U). + -:fire: Nov 1: AutoGen is the top trending repo on GitHub in October 2023. + -:tada: Oct 03: AutoGen spins off from FLAML on GitHub and has a major paper update (first version on Aug 16). + -:tada: Mar 29: AutoGen is first created in [FLAML](https://github.com/microsoft/FLAML). +:tada: Mar 29, 2023: AutoGen is first created in [FLAML](https://github.com/microsoft/FLAML). +

+ + ↑ Back to Top ↑ + +

+ ## What is AutoGen AutoGen is a framework that enables the development of LLM applications using multiple agents that can converse with each other to solve tasks. AutoGen agents are customizable, conversable, and seamlessly allow human participation. They can operate in various modes that employ combinations of LLMs, human inputs, and tools. @@ -57,10 +73,22 @@ AutoGen is a framework that enables the development of LLM applications using mu AutoGen is powered by collaborative [research studies](https://microsoft.github.io/autogen/docs/Research) from Microsoft, Penn State University, and the University of Washington. +

+ + ↑ Back to Top ↑ + +

+ ## Roadmaps To see what we are working on and what we plan to work on, please check our -[Roadmap Issues](https://github.com/microsoft/autogen/issues?q=is%3Aopen+is%3Aissue+label%3Aroadmap). +[Roadmap Issues](https://aka.ms/autogen-roadmap). + +

+ + ↑ Back to Top ↑ + +

## Quickstart The easiest way to start playing is @@ -72,6 +100,13 @@ The easiest way to start playing is 3. Start playing with the notebooks! *NOTE*: OAI_CONFIG_LIST_sample lists GPT-4 as the default model, as this represents our current recommendation, and is known to work well with AutoGen. If you use a model other than GPT-4, you may need to revise various system prompts (especially if using weaker models like GPT-3.5-turbo). Moreover, if you use models other than those hosted by OpenAI or Azure, you may incur additional risks related to alignment and safety. Proceed with caution if updating this default. + +

+ + ↑ Back to Top ↑ + +

+ ## [Installation](https://microsoft.github.io/autogen/docs/Installation) ### Option 1. Install and Run AutoGen in Docker @@ -100,6 +135,12 @@ Even if you are installing and running AutoGen locally outside of docker, the re For LLM inference configurations, check the [FAQs](https://microsoft.github.io/autogen/docs/FAQ#set-your-api-endpoints). +

+ + ↑ Back to Top ↑ + +

+ ## Multi-Agent Conversation Framework Autogen enables the next-gen LLM applications with a generic [multi-agent conversation](https://microsoft.github.io/autogen/docs/Use-Cases/agent_chat) framework. It offers customizable and conversable agents that integrate LLMs, tools, and humans. @@ -139,6 +180,12 @@ The figure below shows an example conversation flow with AutoGen. Alternatively, the [sample code](https://github.com/microsoft/autogen/blob/main/samples/simple_chat.py) here allows a user to chat with an AutoGen agent in ChatGPT style. Please find more [code examples](https://microsoft.github.io/autogen/docs/Examples#automated-multi-agent-chat) for this feature. +

+ + ↑ Back to Top ↑ + +

+ ## Enhanced LLM Inferences Autogen also helps maximize the utility out of the expensive LLMs such as ChatGPT and GPT-4. It offers [enhanced LLM inference](https://microsoft.github.io/autogen/docs/Use-Cases/enhanced_inference#api-unification) with powerful functionalities like caching, error handling, multi-config inference and templating. @@ -162,6 +209,12 @@ response = autogen.Completion.create(context=test_instance, **config) Please find more [code examples](https://microsoft.github.io/autogen/docs/Examples#tune-gpt-models) for this feature. --> +

+ + ↑ Back to Top ↑ + +

+ ## Documentation You can find detailed documentation about AutoGen [here](https://microsoft.github.io/autogen/). @@ -170,12 +223,18 @@ In addition, you can find: - [Research](https://microsoft.github.io/autogen/docs/Research), [blogposts](https://microsoft.github.io/autogen/blog) around AutoGen, and [Transparency FAQs](https://github.com/microsoft/autogen/blob/main/TRANSPARENCY_FAQS.md) -- [Discord](https://discord.gg/pAbnFJrkgZ) +- [Discord](https://aka.ms/autogen-dc) - [Contributing guide](https://microsoft.github.io/autogen/docs/Contribute) - [Roadmap](https://github.com/orgs/microsoft/projects/989/views/3) +

+ + ↑ Back to Top ↑ + +

+ ## Related Papers [AutoGen](https://arxiv.org/abs/2308.08155) @@ -213,6 +272,23 @@ In addition, you can find: } ``` +[AgentOptimizer](https://arxiv.org/pdf/2402.11359) + +``` +@article{zhang2024training, + title={Training Language Model Agents without Modifying Language Models}, + author={Zhang, Shaokun and Zhang, Jieyu and Liu, Jiale and Song, Linxin and Wang, Chi and Krishna, Ranjay and Wu, Qingyun}, + journal={ICML'24}, + year={2024} +} +``` + +

+ + ↑ Back to Top ↑ + +

+ ## Contributing This project welcomes contributions and suggestions. Most contributions require you to agree to a @@ -229,11 +305,23 @@ This project has adopted the [Microsoft Open Source Code of Conduct](https://ope For more information, see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. +

+ + ↑ Back to Top ↑ + +

+ ## Contributors Wall - + +

+ + ↑ Back to Top ↑ + +

+ # Legal Notices Microsoft and any contributors grant you a license to the Microsoft documentation and other content @@ -250,3 +338,9 @@ Privacy information can be found at https://privacy.microsoft.com/en-us/ Microsoft and any contributors reserve all other rights, whether under their respective copyrights, patents, or trademarks, whether by implication, estoppel, or otherwise. + +

+ + ↑ Back to Top ↑ + +

diff --git a/autogen/__init__.py b/autogen/__init__.py index ba920c92e46..02f956c4bcf 100644 --- a/autogen/__init__.py +++ b/autogen/__init__.py @@ -1,10 +1,10 @@ import logging -from .version import __version__ -from .oai import * + from .agentchat import * -from .exception_utils import * from .code_utils import DEFAULT_MODEL, FAST_MODEL - +from .exception_utils import * +from .oai import * +from .version import __version__ # Set the root logger. logger = logging.getLogger(__name__) diff --git a/autogen/_pydantic.py b/autogen/_pydantic.py index 89dbc4fd291..c463dbb3875 100644 --- a/autogen/_pydantic.py +++ b/autogen/_pydantic.py @@ -13,7 +13,7 @@ from pydantic._internal._typing_extra import eval_type_lenient as evaluate_forwardref from pydantic.json_schema import JsonSchemaValue - def type2schema(t: Optional[Type]) -> JsonSchemaValue: + def type2schema(t: Any) -> JsonSchemaValue: """Convert a type to a JSON schema Args: @@ -51,11 +51,11 @@ def model_dump_json(model: BaseModel) -> str: # Remove this once we drop support for pydantic 1.x else: # pragma: no cover from pydantic import schema_of - from pydantic.typing import evaluate_forwardref as evaluate_forwardref + from pydantic.typing import evaluate_forwardref as evaluate_forwardref # type: ignore[no-redef] - JsonSchemaValue = Dict[str, Any] + JsonSchemaValue = Dict[str, Any] # type: ignore[misc] - def type2schema(t: Optional[Type]) -> JsonSchemaValue: + def type2schema(t: Any) -> JsonSchemaValue: """Convert a type to a JSON schema Args: @@ -64,27 +64,27 @@ def type2schema(t: Optional[Type]) -> JsonSchemaValue: Returns: JsonSchemaValue: The JSON schema """ - if PYDANTIC_V1: - if t is None: - return {"type": "null"} - elif get_origin(t) is Union: - return {"anyOf": [type2schema(tt) for tt in get_args(t)]} - elif get_origin(t) in [Tuple, tuple]: - prefixItems = [type2schema(tt) for tt in get_args(t)] - return { - "maxItems": len(prefixItems), - "minItems": len(prefixItems), - "prefixItems": prefixItems, - "type": "array", - } - - d = schema_of(t) - if "title" in d: - d.pop("title") - if "description" in d: - d.pop("description") - - return d + + if t is None: + return {"type": "null"} + elif get_origin(t) is Union: + return {"anyOf": [type2schema(tt) for tt in get_args(t)]} + elif get_origin(t) in [Tuple, tuple]: + prefixItems = [type2schema(tt) for tt in get_args(t)] + return { + "maxItems": len(prefixItems), + "minItems": len(prefixItems), + "prefixItems": prefixItems, + "type": "array", + } + else: + d = schema_of(t) + if "title" in d: + d.pop("title") + if "description" in d: + d.pop("description") + + return d def model_dump(model: BaseModel) -> Dict[str, Any]: """Convert a pydantic model to a dict diff --git a/autogen/agentchat/__init__.py b/autogen/agentchat/__init__.py index 817fc8abdb6..d31a59d98fb 100644 --- a/autogen/agentchat/__init__.py +++ b/autogen/agentchat/__init__.py @@ -1,9 +1,9 @@ from .agent import Agent from .assistant_agent import AssistantAgent +from .chat import ChatResult, initiate_chats from .conversable_agent import ConversableAgent, register_function from .groupchat import GroupChat, GroupChatManager from .user_proxy_agent import UserProxyAgent -from .chat import initiate_chats, ChatResult from .utils import gather_usage_summary __all__ = ( diff --git a/autogen/agentchat/assistant_agent.py b/autogen/agentchat/assistant_agent.py index 25f7edbf073..b5ec7de90c7 100644 --- a/autogen/agentchat/assistant_agent.py +++ b/autogen/agentchat/assistant_agent.py @@ -1,7 +1,8 @@ from typing import Callable, Dict, Literal, Optional, Union +from autogen.runtime_logging import log_new_agent, logging_enabled + from .conversable_agent import ConversableAgent -from autogen.runtime_logging import logging_enabled, log_new_agent class AssistantAgent(ConversableAgent): diff --git a/autogen/agentchat/chat.py b/autogen/agentchat/chat.py index bd56cf2f579..b527f8e0bae 100644 --- a/autogen/agentchat/chat.py +++ b/autogen/agentchat/chat.py @@ -1,15 +1,15 @@ import asyncio -from functools import partial -import logging -from collections import defaultdict, abc -from typing import Dict, List, Any, Set, Tuple -from dataclasses import dataclass -from .utils import consolidate_chat_info import datetime +import logging import warnings -from ..io.base import IOStream -from ..formatting_utils import colored +from collections import abc, defaultdict +from dataclasses import dataclass +from functools import partial +from typing import Any, Dict, List, Set, Tuple +from ..formatting_utils import colored +from ..io.base import IOStream +from .utils import consolidate_chat_info logger = logging.getLogger(__name__) Prerequisite = Tuple[int, int] @@ -25,8 +25,12 @@ class ChatResult: """The chat history.""" summary: str = None """A summary obtained from the chat.""" - cost: tuple = None # (dict, dict) - (total_cost, actual_cost_with_cache) - """The cost of the chat. a tuple of (total_cost, total_actual_cost), where total_cost is a dictionary of cost information, and total_actual_cost is a dictionary of information on the actual incurred cost with cache.""" + cost: Dict[str, dict] = None # keys: "usage_including_cached_inference", "usage_excluding_cached_inference" + """The cost of the chat. + The value for each usage type is a dictionary containing cost information for that specific type. + - "usage_including_cached_inference": Cost information on the total usage, including the tokens in cached inference. + - "usage_excluding_cached_inference": Cost information on the usage of tokens, excluding the tokens in cache. No larger than "usage_including_cached_inference". + """ human_input: List[str] = None """A list of human input solicited during the chat.""" @@ -141,25 +145,35 @@ def __post_carryover_processing(chat_info: Dict[str, Any]) -> None: def initiate_chats(chat_queue: List[Dict[str, Any]]) -> List[ChatResult]: """Initiate a list of chats. - Args: - chat_queue (List[Dict]): a list of dictionaries containing the information about the chats. - - Each dictionary should contain the input arguments for [`ConversableAgent.initiate_chat`](/docs/reference/agentchat/conversable_agent#initiate_chat). For example: - - "sender": the sender agent. - - "recipient": the recipient agent. - - "clear_history" (bool): whether to clear the chat history with the agent. Default is True. - - "silent" (bool or None): (Experimental) whether to print the messages in this conversation. Default is False. - - "cache" (AbstractCache or None): the cache client to use for this conversation. Default is None. - - "max_turns" (int or None): maximum number of turns for the chat. If None, the chat will continue until a termination condition is met. Default is None. - - "summary_method" (str or callable): a string or callable specifying the method to get a summary from the chat. Default is DEFAULT_summary_method, i.e., "last_msg". - - "summary_args" (dict): a dictionary of arguments to be passed to the summary_method. Default is {}. - - "message" (str, callable or None): if None, input() will be called to get the initial message. - - **context: additional context information to be passed to the chat. - - "carryover": It can be used to specify the carryover information to be passed to this chat. - If provided, we will combine this carryover with the "message" content when generating the initial chat - message in `generate_init_message`. - + chat_queue (List[Dict]): A list of dictionaries containing the information about the chats. + + Each dictionary should contain the input arguments for + [`ConversableAgent.initiate_chat`](/docs/reference/agentchat/conversable_agent#initiate_chat). + For example: + - `"sender"` - the sender agent. + - `"recipient"` - the recipient agent. + - `"clear_history" (bool) - whether to clear the chat history with the agent. + Default is True. + - `"silent"` (bool or None) - (Experimental) whether to print the messages in this + conversation. Default is False. + - `"cache"` (Cache or None) - the cache client to use for this conversation. + Default is None. + - `"max_turns"` (int or None) - maximum number of turns for the chat. If None, the chat + will continue until a termination condition is met. Default is None. + - `"summary_method"` (str or callable) - a string or callable specifying the method to get + a summary from the chat. Default is DEFAULT_summary_method, i.e., "last_msg". + - `"summary_args"` (dict) - a dictionary of arguments to be passed to the summary_method. + Default is {}. + - `"message"` (str, callable or None) - if None, input() will be called to get the + initial message. + - `**context` - additional context information to be passed to the chat. + - `"carryover"` - It can be used to specify the carryover information to be passed + to this chat. If provided, we will combine this carryover with the "message" content when + generating the initial chat message in `generate_init_message`. + - `"finished_chat_indexes_to_exclude_from_carryover"` - It can be used by specifying a list of indexes of the finished_chats list, + from which to exclude the summaries for carryover. If 'finished_chat_indexes_to_exclude_from_carryover' is not provided or an empty list, + then summary from all the finished chats will be taken. Returns: (list): a list of ChatResult objects corresponding to the finished chats in the chat_queue. """ @@ -171,9 +185,16 @@ def initiate_chats(chat_queue: List[Dict[str, Any]]) -> List[ChatResult]: while current_chat_queue: chat_info = current_chat_queue.pop(0) _chat_carryover = chat_info.get("carryover", []) + finished_chat_indexes_to_exclude_from_carryover = chat_info.get( + "finished_chat_indexes_to_exclude_from_carryover", [] + ) + if isinstance(_chat_carryover, str): _chat_carryover = [_chat_carryover] - chat_info["carryover"] = _chat_carryover + [r.summary for r in finished_chats] + chat_info["carryover"] = _chat_carryover + [ + r.summary for i, r in enumerate(finished_chats) if i not in finished_chat_indexes_to_exclude_from_carryover + ] + __post_carryover_processing(chat_info) sender = chat_info["sender"] chat_res = sender.initiate_chat(**chat_info) @@ -228,11 +249,11 @@ async def a_initiate_chats(chat_queue: List[Dict[str, Any]]) -> Dict[int, ChatRe """(async) Initiate a list of chats. args: - Please refer to `initiate_chats`. + - Please refer to `initiate_chats`. returns: - (Dict): a dict of ChatId: ChatResult corresponding to the finished chats in the chat_queue. + - (Dict): a dict of ChatId: ChatResult corresponding to the finished chats in the chat_queue. """ consolidate_chat_info(chat_queue) _validate_recipients(chat_queue) diff --git a/autogen/agentchat/contrib/agent_builder.py b/autogen/agentchat/contrib/agent_builder.py index 7a3850d79ae..272d954ff27 100644 --- a/autogen/agentchat/contrib/agent_builder.py +++ b/autogen/agentchat/contrib/agent_builder.py @@ -1,10 +1,11 @@ -import autogen -import time -import subprocess as sp -import socket -import json import hashlib -from typing import Optional, List, Dict, Tuple +import json +import socket +import subprocess as sp +import time +from typing import Dict, List, Optional, Tuple + +import autogen def _config_check(config: Dict): @@ -202,9 +203,6 @@ def _create_agent( Returns: agent: a set-up agent. """ - from huggingface_hub import HfApi - from huggingface_hub.utils import GatedRepoError, RepositoryNotFoundError - config_list = autogen.config_list_from_json( self.config_file_or_env, file_location=self.config_file_location, @@ -217,10 +215,15 @@ def _create_agent( f"If you load configs from json, make sure the model in agent_configs is in the {self.config_file_or_env}." ) try: + from huggingface_hub import HfApi + from huggingface_hub.utils import GatedRepoError, RepositoryNotFoundError + hf_api = HfApi() hf_api.model_info(model_name_or_hf_repo) model_name = model_name_or_hf_repo.split("/")[-1] server_id = f"{model_name}_{self.host}" + except ImportError: + server_id = self.online_server_name except GatedRepoError as e: raise e except RepositoryNotFoundError: @@ -494,9 +497,6 @@ def build_from_library( agent_list: a list of agents. cached_configs: cached configs. """ - import chromadb - from chromadb.utils import embedding_functions - if code_execution_config is None: code_execution_config = { "last_n_messages": 2, @@ -527,6 +527,9 @@ def build_from_library( print("==> Looking for suitable agents in library...") if embedding_model is not None: + import chromadb + from chromadb.utils import embedding_functions + chroma_client = chromadb.Client() collection = chroma_client.create_collection( name="agent_list", diff --git a/autogen/agentchat/contrib/agent_eval/README.md b/autogen/agentchat/contrib/agent_eval/README.md new file mode 100644 index 00000000000..6588a1ec611 --- /dev/null +++ b/autogen/agentchat/contrib/agent_eval/README.md @@ -0,0 +1,7 @@ +Agents for running the AgentEval pipeline. + +AgentEval is a process for evaluating a LLM-based system's performance on a given task. + +When given a task to evaluate and a few example runs, the critic and subcritic agents create evaluation criteria for evaluating a system's solution. Once the criteria has been created, the quantifier agent can evaluate subsequent task solutions based on the generated criteria. + +For more information see: [AgentEval Integration Roadmap](https://github.com/microsoft/autogen/issues/2162) diff --git a/autogen/agentchat/contrib/agent_eval/agent_eval.py b/autogen/agentchat/contrib/agent_eval/agent_eval.py new file mode 100644 index 00000000000..b48c65a66d2 --- /dev/null +++ b/autogen/agentchat/contrib/agent_eval/agent_eval.py @@ -0,0 +1,101 @@ +from typing import Dict, List, Literal, Optional, Union + +import autogen +from autogen.agentchat.contrib.agent_eval.criterion import Criterion +from autogen.agentchat.contrib.agent_eval.critic_agent import CriticAgent +from autogen.agentchat.contrib.agent_eval.quantifier_agent import QuantifierAgent +from autogen.agentchat.contrib.agent_eval.subcritic_agent import SubCriticAgent +from autogen.agentchat.contrib.agent_eval.task import Task + + +def generate_criteria( + llm_config: Optional[Union[Dict, Literal[False]]] = None, + task: Task = None, + additional_instructions: str = "", + max_round=2, + use_subcritic: bool = False, +): + """ + Creates a list of criteria for evaluating the utility of a given task. + Args: + llm_config (dict or bool): llm inference configuration. + task (Task): The task to evaluate. + additional_instructions (str): Additional instructions for the criteria agent. + max_round (int): The maximum number of rounds to run the conversation. + use_subcritic (bool): Whether to use the subcritic agent to generate subcriteria. + Returns: + list: A list of Criterion objects for evaluating the utility of the given task. + """ + critic = CriticAgent( + system_message=CriticAgent.DEFAULT_SYSTEM_MESSAGE + "\n" + additional_instructions, + llm_config=llm_config, + ) + + critic_user = autogen.UserProxyAgent( + name="critic_user", + max_consecutive_auto_reply=0, # terminate without auto-reply + human_input_mode="NEVER", + code_execution_config={"use_docker": False}, + ) + + agents = [critic_user, critic] + + if use_subcritic: + subcritic = SubCriticAgent( + llm_config=llm_config, + ) + agents.append(subcritic) + + groupchat = autogen.GroupChat( + agents=agents, messages=[], max_round=max_round, speaker_selection_method="round_robin" + ) + critic_manager = autogen.GroupChatManager(groupchat=groupchat, llm_config=llm_config) + + critic_user.initiate_chat(critic_manager, message=task.get_sys_message()) + criteria = critic_user.last_message() + content = criteria["content"] + # need to strip out any extra code around the returned json + content = content[content.find("[") : content.rfind("]") + 1] + criteria = Criterion.parse_json_str(content) + return criteria + + +def quantify_criteria( + llm_config: Optional[Union[Dict, Literal[False]]] = None, + criteria: List[Criterion] = None, + task: Task = None, + test_case: str = "", + ground_truth: str = "", +): + """ + Quantifies the performance of a system using the provided criteria. + Args: + llm_config (dict or bool): llm inference configuration. + criteria ([Criterion]): A list of criteria for evaluating the utility of a given task. + task (Task): The task to evaluate. + test_case (str): The test case to evaluate. + ground_truth (str): The ground truth for the test case. + Returns: + dict: A dictionary where the keys are the criteria and the values are the assessed performance based on accepted values for each criteria. + """ + quantifier = QuantifierAgent( + llm_config=llm_config, + ) + + quantifier_user = autogen.UserProxyAgent( + name="quantifier_user", + max_consecutive_auto_reply=0, # terminate without auto-reply + human_input_mode="NEVER", + code_execution_config={"use_docker": False}, + ) + + quantifier_user.initiate_chat( # noqa: F841 + quantifier, + message=task.get_sys_message() + + "Evaluation dictionary: " + + Criterion.write_json(criteria) + + "actual test case to evaluate: " + + test_case, + ) + quantified_results = quantifier_user.last_message() + return {"actual_success": ground_truth, "estimated_performance": quantified_results["content"]} diff --git a/autogen/agentchat/contrib/agent_eval/criterion.py b/autogen/agentchat/contrib/agent_eval/criterion.py new file mode 100644 index 00000000000..5efd121ec07 --- /dev/null +++ b/autogen/agentchat/contrib/agent_eval/criterion.py @@ -0,0 +1,41 @@ +from __future__ import annotations + +import json +from typing import List + +import pydantic_core +from pydantic import BaseModel +from pydantic.json import pydantic_encoder + + +class Criterion(BaseModel): + """ + A class that represents a criterion for agent evaluation. + """ + + name: str + description: str + accepted_values: List[str] + sub_criteria: List[Criterion] = list() + + @staticmethod + def parse_json_str(criteria: str): + """ + Create a list of Criterion objects from a json string. + Args: + criteria (str): Json string that represents the criteria + returns: + [Criterion]: A list of Criterion objects that represents the json criteria information. + """ + return [Criterion(**crit) for crit in json.loads(criteria)] + + @staticmethod + def write_json(criteria): + """ + Create a json string from a list of Criterion objects. + Args: + criteria ([Criterion]): A list of Criterion objects. + Returns: + str: A json string that represents the list of Criterion objects. + """ + return json.dumps([crit.model_dump() for crit in criteria], indent=2) diff --git a/autogen/agentchat/contrib/agent_eval/critic_agent.py b/autogen/agentchat/contrib/agent_eval/critic_agent.py new file mode 100644 index 00000000000..2f5e5598ba6 --- /dev/null +++ b/autogen/agentchat/contrib/agent_eval/critic_agent.py @@ -0,0 +1,41 @@ +from typing import Optional + +from autogen.agentchat.conversable_agent import ConversableAgent + + +class CriticAgent(ConversableAgent): + """ + An agent for creating list of criteria for evaluating the utility of a given task. + """ + + DEFAULT_SYSTEM_MESSAGE = """You are a helpful assistant. You suggest criteria for evaluating different tasks. They should be distinguishable, quantifiable and not redundant. + Convert the evaluation criteria into a list where each item is a criteria which consists of the following dictionary as follows + {"name": name of the criterion, "description": criteria description , "accepted_values": possible accepted inputs for this key} + Make sure "accepted_values" include the acceptable inputs for each key that are fine-grained and preferably multi-graded levels and "description" includes the criterion description. + Output just the criteria string you have created, no code. + """ + + DEFAULT_DESCRIPTION = "An AI agent for creating list criteria for evaluating the utility of a given task." + + def __init__( + self, + name="critic", + system_message: Optional[str] = DEFAULT_SYSTEM_MESSAGE, + description: Optional[str] = DEFAULT_DESCRIPTION, + **kwargs, + ): + """ + Args: + name (str): agent name. + system_message (str): system message for the ChatCompletion inference. + Please override this attribute if you want to reprogram the agent. + description (str): The description of the agent. + **kwargs (dict): Please refer to other kwargs in + [ConversableAgent](../../conversable_agent#__init__). + """ + super().__init__( + name=name, + system_message=system_message, + description=description, + **kwargs, + ) diff --git a/autogen/agentchat/contrib/agent_eval/quantifier_agent.py b/autogen/agentchat/contrib/agent_eval/quantifier_agent.py new file mode 100644 index 00000000000..02a8f650fab --- /dev/null +++ b/autogen/agentchat/contrib/agent_eval/quantifier_agent.py @@ -0,0 +1,36 @@ +from typing import Optional + +from autogen.agentchat.conversable_agent import ConversableAgent + + +class QuantifierAgent(ConversableAgent): + """ + An agent for quantifying the performance of a system using the provided criteria. + """ + + DEFAULT_SYSTEM_MESSAGE = """"You are a helpful assistant. You quantify the output of different tasks based on the given criteria. + The criterion is given in a json list format where each element is a distinct criteria. + The each element is a dictionary as follows {"name": name of the criterion, "description": criteria description , "accepted_values": possible accepted inputs for this key} + You are going to quantify each of the crieria for a given task based on the task description. + Return a dictionary where the keys are the criteria and the values are the assessed performance based on accepted values for each criteria. + Return only the dictionary, no code.""" + + DEFAULT_DESCRIPTION = "An AI agent for quantifing the performance of a system using the provided criteria." + + def __init__( + self, + name="quantifier", + system_message: Optional[str] = DEFAULT_SYSTEM_MESSAGE, + description: Optional[str] = DEFAULT_DESCRIPTION, + **kwargs, + ): + """ + Args: + name (str): agent name. + system_message (str): system message for the ChatCompletion inference. + Please override this attribute if you want to reprogram the agent. + description (str): The description of the agent. + **kwargs (dict): Please refer to other kwargs in + [ConversableAgent](../../conversable_agent#__init__). + """ + super().__init__(name=name, system_message=system_message, description=description, **kwargs) diff --git a/autogen/agentchat/contrib/agent_eval/subcritic_agent.py b/autogen/agentchat/contrib/agent_eval/subcritic_agent.py new file mode 100755 index 00000000000..fa994ee7bda --- /dev/null +++ b/autogen/agentchat/contrib/agent_eval/subcritic_agent.py @@ -0,0 +1,42 @@ +from typing import Optional + +from autogen.agentchat.conversable_agent import ConversableAgent + + +class SubCriticAgent(ConversableAgent): + """ + An agent for creating subcriteria from a given list of criteria for evaluating the utility of a given task. + """ + + DEFAULT_SYSTEM_MESSAGE = """You are a helpful assistant to the critic agent. You suggest sub criteria for evaluating different tasks based on the criteria provided by the critic agent (if you feel it is needed). + They should be distinguishable, quantifiable, and related to the overall theme of the critic's provided criteria. + You operate by taking in the description of the criteria. You then create a new key called sub criteria where you provide the sub criteria for the given criteria. + The value of the sub_criteria is a dictionary where the keys are the subcriteria and each value is as follows {"description": sub criteria description , "accepted_values": possible accepted inputs for this key} + Do this for each criteria provided by the critic (removing the criteria's accepted values). "accepted_values" include the acceptable inputs for each key that are fine-grained and preferably multi-graded levels. "description" includes the criterion description. + Once you have created the sub criteria for the given criteria, you return the json (make sure to include the contents of the critic's dictionary in the final dictionary as well). + Make sure to return a valid json and no code""" + + DEFAULT_DESCRIPTION = "An AI agent for creating subcriteria from a given list of criteria." + + def __init__( + self, + name="subcritic", + system_message: Optional[str] = DEFAULT_SYSTEM_MESSAGE, + description: Optional[str] = DEFAULT_DESCRIPTION, + **kwargs, + ): + """ + Args: + name (str): agent name. + system_message (str): system message for the ChatCompletion inference. + Please override this attribute if you want to reprogram the agent. + description (str): The description of the agent. + **kwargs (dict): Please refer to other kwargs in + [ConversableAgent](../../conversable_agent#__init__). + """ + super().__init__( + name=name, + system_message=system_message, + description=description, + **kwargs, + ) diff --git a/autogen/agentchat/contrib/agent_eval/task.py b/autogen/agentchat/contrib/agent_eval/task.py new file mode 100644 index 00000000000..9f96fbf79e2 --- /dev/null +++ b/autogen/agentchat/contrib/agent_eval/task.py @@ -0,0 +1,37 @@ +import json + +from pydantic import BaseModel + + +class Task(BaseModel): + """ + Class representing a task for agent completion, includes example agent execution for criteria generation. + """ + + name: str + description: str + successful_response: str + failed_response: str + + def get_sys_message(self): + return f"""Task: {self.name}. + Task description: {self.description} + Task successful example: {self.successful_response} + Task failed example: {self.failed_response} + """ + + @staticmethod + def parse_json_str(task: str): + """ + Create a Task object from a json object. + Args: + json_data (dict): A dictionary that represents the task. + Returns: + Task: A Task object that represents the json task information. + """ + json_data = json.loads(task) + name = json_data.get("name") + description = json_data.get("description") + successful_response = json_data.get("successful_response") + failed_response = json_data.get("failed_response") + return Task(name, description, successful_response, failed_response) diff --git a/autogen/agentchat/contrib/agent_optimizer.py b/autogen/agentchat/contrib/agent_optimizer.py index 711874efc8f..af264d4b65f 100644 --- a/autogen/agentchat/contrib/agent_optimizer.py +++ b/autogen/agentchat/contrib/agent_optimizer.py @@ -1,8 +1,9 @@ -from autogen.code_utils import execute_code -from typing import List, Dict, Optional -import json import copy +import json +from typing import Dict, List, Literal, Optional, Union + import autogen +from autogen.code_utils import execute_code ADD_FUNC = { "type": "function", @@ -171,16 +172,16 @@ class AgentOptimizer: def __init__( self, max_actions_per_step: int, - config_file_or_env: Optional[str] = "OAI_CONFIG_LIST", - config_file_location: Optional[str] = "", + llm_config: dict, optimizer_model: Optional[str] = "gpt-4-1106-preview", ): """ (These APIs are experimental and may change in the future.) Args: max_actions_per_step (int): the maximum number of actions that the optimizer can take in one step. - config_file_or_env: path or environment of the OpenAI api configs. - config_file_location: the location of the OpenAI config file. + llm_config (dict): llm inference configuration. + Please refer to [OpenAIWrapper.create](/docs/reference/oai/client#create) for available options. + When using OpenAI or Azure OpenAI endpoints, please specify a non-empty 'model' either in `llm_config` or in each config of 'config_list' in `llm_config`. optimizer_model: the model used for the optimizer. """ self.max_actions_per_step = max_actions_per_step @@ -198,14 +199,17 @@ def __init__( self._failure_functions_performance = [] self._best_performance = -1 - config_list = autogen.config_list_from_json( - config_file_or_env, - file_location=config_file_location, - filter_dict={"model": [self.optimizer_model]}, + assert isinstance(llm_config, dict), "llm_config must be a dict" + llm_config = copy.deepcopy(llm_config) + self.llm_config = llm_config + if self.llm_config in [{}, {"config_list": []}, {"config_list": [{"model": ""}]}]: + raise ValueError( + "When using OpenAI or Azure OpenAI endpoints, specify a non-empty 'model' either in 'llm_config' or in each config of 'config_list'." + ) + self.llm_config["config_list"] = autogen.filter_config( + llm_config["config_list"], {"model": [self.optimizer_model]} ) - if len(config_list) == 0: - raise RuntimeError("No valid openai config found in the config file or environment variable.") - self._client = autogen.OpenAIWrapper(config_list=config_list) + self._client = autogen.OpenAIWrapper(**self.llm_config) def record_one_conversation(self, conversation_history: List[Dict], is_satisfied: bool = None): """ @@ -265,7 +269,7 @@ def step(self): actions_num=action_index, best_functions=best_functions, incumbent_functions=incumbent_functions, - accumerated_experience=failure_experience_prompt, + accumulated_experience=failure_experience_prompt, statistic_informations=statistic_prompt, ) messages = [{"role": "user", "content": prompt}] diff --git a/autogen/agentchat/contrib/capabilities/context_handling.py b/autogen/agentchat/contrib/capabilities/context_handling.py index 1510ae5fcd6..173811842eb 100644 --- a/autogen/agentchat/contrib/capabilities/context_handling.py +++ b/autogen/agentchat/contrib/capabilities/context_handling.py @@ -1,9 +1,18 @@ import sys -from termcolor import colored -from typing import Dict, Optional, List -from autogen import ConversableAgent -from autogen import token_count_utils +from typing import Dict, List, Optional +from warnings import warn + import tiktoken +from termcolor import colored + +from autogen import ConversableAgent, token_count_utils + +warn( + "Context handling with TransformChatHistory is deprecated. " + "Please use TransformMessages from autogen/agentchat/contrib/capabilities/transform_messages.py instead.", + DeprecationWarning, + stacklevel=2, +) class TransformChatHistory: @@ -26,7 +35,8 @@ class TransformChatHistory: 3. Third, it limits the total number of tokens in the chat history When adding this capability to an agent, the following are modified: - - A hook is added to the hookable method `process_all_messages_before_reply` to transform the received messages for possible truncation. + - A hook is added to the hookable method `process_all_messages_before_reply` to transform the + received messages for possible truncation. Not modifying the stored message history. """ diff --git a/autogen/agentchat/contrib/capabilities/generate_images.py b/autogen/agentchat/contrib/capabilities/generate_images.py index d16121ddb9a..e4a8f1195c2 100644 --- a/autogen/agentchat/contrib/capabilities/generate_images.py +++ b/autogen/agentchat/contrib/capabilities/generate_images.py @@ -5,10 +5,10 @@ from PIL.Image import Image from autogen import Agent, ConversableAgent, code_utils -from autogen.cache import AbstractCache from autogen.agentchat.contrib import img_utils from autogen.agentchat.contrib.capabilities.agent_capability import AgentCapability from autogen.agentchat.contrib.text_analyzer_agent import TextAnalyzerAgent +from autogen.cache import AbstractCache SYSTEM_MESSAGE = "You've been given the special ability to generate images." DESCRIPTION_MESSAGE = "This agent has the ability to generate images." diff --git a/autogen/agentchat/contrib/capabilities/teachability.py b/autogen/agentchat/contrib/capabilities/teachability.py index 58ba35ed425..596e449ce34 100644 --- a/autogen/agentchat/contrib/capabilities/teachability.py +++ b/autogen/agentchat/contrib/capabilities/teachability.py @@ -1,11 +1,14 @@ import os +import pickle from typing import Dict, Optional, Union + import chromadb from chromadb.config import Settings -import pickle + from autogen.agentchat.assistant_agent import ConversableAgent from autogen.agentchat.contrib.capabilities.agent_capability import AgentCapability from autogen.agentchat.contrib.text_analyzer_agent import TextAnalyzerAgent + from ....formatting_utils import colored @@ -83,7 +86,7 @@ def prepopulate_db(self): """Adds a few arbitrary memos to the DB.""" self.memo_store.prepopulate() - def process_last_received_message(self, text): + def process_last_received_message(self, text: Union[Dict, str]): """ Appends any relevant memos to the message text, and stores any apparent teachings in new memos. Uses TextAnalyzerAgent to make decisions about memo storage and retrieval. @@ -100,7 +103,7 @@ def process_last_received_message(self, text): # Return the (possibly) expanded message text. return expanded_text - def _consider_memo_storage(self, comment): + def _consider_memo_storage(self, comment: Union[Dict, str]): """Decides whether to store something from one user comment in the DB.""" memo_added = False @@ -158,7 +161,7 @@ def _consider_memo_storage(self, comment): # Yes. Save them to disk. self.memo_store._save_memos() - def _consider_memo_retrieval(self, comment): + def _consider_memo_retrieval(self, comment: Union[Dict, str]): """Decides whether to retrieve memos from the DB, and add them to the chat context.""" # First, use the comment directly as the lookup key. @@ -192,7 +195,7 @@ def _consider_memo_retrieval(self, comment): # Append the memos to the text of the last message. return comment + self._concatenate_memo_texts(memo_list) - def _retrieve_relevant_memos(self, input_text): + def _retrieve_relevant_memos(self, input_text: str) -> list: """Returns semantically related memos from the DB.""" memo_list = self.memo_store.get_related_memos( input_text, n_results=self.max_num_retrievals, threshold=self.recall_threshold @@ -210,7 +213,7 @@ def _retrieve_relevant_memos(self, input_text): memo_list = [memo[1] for memo in memo_list] return memo_list - def _concatenate_memo_texts(self, memo_list): + def _concatenate_memo_texts(self, memo_list: list) -> str: """Concatenates the memo texts into a single string for inclusion in the chat context.""" memo_texts = "" if len(memo_list) > 0: @@ -222,7 +225,7 @@ def _concatenate_memo_texts(self, memo_list): memo_texts = memo_texts + "\n" + info return memo_texts - def _analyze(self, text_to_analyze, analysis_instructions): + def _analyze(self, text_to_analyze: Union[Dict, str], analysis_instructions: Union[Dict, str]): """Asks TextAnalyzerAgent to analyze the given text according to specific instructions.""" self.analyzer.reset() # Clear the analyzer's list of messages. self.teachable_agent.send( @@ -243,10 +246,16 @@ class MemoStore: Vector embeddings are currently supplied by Chroma's default Sentence Transformers. """ - def __init__(self, verbosity, reset, path_to_db_dir): + def __init__( + self, + verbosity: Optional[int] = 0, + reset: Optional[bool] = False, + path_to_db_dir: Optional[str] = "./tmp/teachable_agent_db", + ): """ Args: - verbosity (Optional, int): 1 to print memory operations, 0 to omit them. 3+ to print memo lists. + - reset (Optional, bool): True to clear the DB before starting. Default False. - path_to_db_dir (Optional, str): path to the directory where the DB is stored. """ self.verbosity = verbosity @@ -301,7 +310,7 @@ def reset_db(self): self.uid_text_dict = {} self._save_memos() - def add_input_output_pair(self, input_text, output_text): + def add_input_output_pair(self, input_text: str, output_text: str): """Adds an input-output pair to the vector DB.""" self.last_memo_id += 1 self.vec_db.add(documents=[input_text], ids=[str(self.last_memo_id)]) @@ -318,7 +327,7 @@ def add_input_output_pair(self, input_text, output_text): if self.verbosity >= 3: self.list_memos() - def get_nearest_memo(self, query_text): + def get_nearest_memo(self, query_text: str): """Retrieves the nearest memo to the given query text.""" results = self.vec_db.query(query_texts=[query_text], n_results=1) uid, input_text, distance = results["ids"][0][0], results["documents"][0][0], results["distances"][0][0] @@ -335,7 +344,7 @@ def get_nearest_memo(self, query_text): ) return input_text, output_text, distance - def get_related_memos(self, query_text, n_results, threshold): + def get_related_memos(self, query_text: str, n_results: int, threshold: Union[int, float]): """Retrieves memos that are related to the given query text within the specified distance threshold.""" if n_results > len(self.uid_text_dict): n_results = len(self.uid_text_dict) diff --git a/autogen/agentchat/contrib/capabilities/text_compressors.py b/autogen/agentchat/contrib/capabilities/text_compressors.py new file mode 100644 index 00000000000..78554bdc935 --- /dev/null +++ b/autogen/agentchat/contrib/capabilities/text_compressors.py @@ -0,0 +1,68 @@ +from typing import Any, Dict, Optional, Protocol + +IMPORT_ERROR: Optional[Exception] = None +try: + import llmlingua +except ImportError: + IMPORT_ERROR = ImportError( + "LLMLingua is not installed. Please install it with `pip install pyautogen[long-context]`" + ) + PromptCompressor = object +else: + from llmlingua import PromptCompressor + + +class TextCompressor(Protocol): + """Defines a protocol for text compression to optimize agent interactions.""" + + def compress_text(self, text: str, **compression_params) -> Dict[str, Any]: + """This method takes a string as input and returns a dictionary containing the compressed text and other + relevant information. The compressed text should be stored under the 'compressed_text' key in the dictionary. + To calculate the number of saved tokens, the dictionary should include 'origin_tokens' and 'compressed_tokens' keys. + """ + ... + + +class LLMLingua: + """Compresses text messages using LLMLingua for improved efficiency in processing and response generation. + + NOTE: The effectiveness of compression and the resultant token savings can vary based on the content of the messages + and the specific configurations used for the PromptCompressor. + """ + + def __init__( + self, + prompt_compressor_kwargs: Dict = dict( + model_name="microsoft/llmlingua-2-bert-base-multilingual-cased-meetingbank", + use_llmlingua2=True, + device_map="cpu", + ), + structured_compression: bool = False, + ) -> None: + """ + Args: + prompt_compressor_kwargs (dict): A dictionary of keyword arguments for the PromptCompressor. Defaults to a + dictionary with model_name set to "microsoft/llmlingua-2-bert-base-multilingual-cased-meetingbank", + use_llmlingua2 set to True, and device_map set to "cpu". + structured_compression (bool): A flag indicating whether to use structured compression. If True, the + structured_compress_prompt method of the PromptCompressor is used. Otherwise, the compress_prompt method + is used. Defaults to False. + dictionary. + + Raises: + ImportError: If the llmlingua library is not installed. + """ + if IMPORT_ERROR: + raise IMPORT_ERROR + + self._prompt_compressor = PromptCompressor(**prompt_compressor_kwargs) + + assert isinstance(self._prompt_compressor, llmlingua.PromptCompressor) + self._compression_method = ( + self._prompt_compressor.structured_compress_prompt + if structured_compression + else self._prompt_compressor.compress_prompt + ) + + def compress_text(self, text: str, **compression_params) -> Dict[str, Any]: + return self._compression_method([text], **compression_params) diff --git a/autogen/agentchat/contrib/capabilities/transform_messages.py b/autogen/agentchat/contrib/capabilities/transform_messages.py new file mode 100644 index 00000000000..e96dc39fa7b --- /dev/null +++ b/autogen/agentchat/contrib/capabilities/transform_messages.py @@ -0,0 +1,87 @@ +import copy +from typing import Dict, List + +from autogen import ConversableAgent + +from ....formatting_utils import colored +from .transforms import MessageTransform + + +class TransformMessages: + """Agent capability for transforming messages before reply generation. + + This capability allows you to apply a series of message transformations to + a ConversableAgent's incoming messages before they are processed for response + generation. This is useful for tasks such as: + + - Limiting the number of messages considered for context. + - Truncating messages to meet token limits. + - Filtering sensitive information. + - Customizing message formatting. + + To use `TransformMessages`: + + 1. Create message transformations (e.g., `MessageHistoryLimiter`, `MessageTokenLimiter`). + 2. Instantiate `TransformMessages` with a list of these transformations. + 3. Add the `TransformMessages` instance to your `ConversableAgent` using `add_to_agent`. + + NOTE: Order of message transformations is important. You could get different results based on + the order of transformations. + + Example: + ```python + from agentchat import ConversableAgent + from agentchat.contrib.capabilities import TransformMessages, MessageHistoryLimiter, MessageTokenLimiter + + max_messages = MessageHistoryLimiter(max_messages=2) + truncate_messages = MessageTokenLimiter(max_tokens=500) + transform_messages = TransformMessages(transforms=[max_messages, truncate_messages]) + + agent = ConversableAgent(...) + transform_messages.add_to_agent(agent) + ``` + """ + + def __init__(self, *, transforms: List[MessageTransform] = [], verbose: bool = True): + """ + Args: + transforms: A list of message transformations to apply. + verbose: Whether to print logs of each transformation or not. + """ + self._transforms = transforms + self._verbose = verbose + + def add_to_agent(self, agent: ConversableAgent): + """Adds the message transformations capability to the specified ConversableAgent. + + This function performs the following modifications to the agent: + + 1. Registers a hook that automatically transforms all messages before they are processed for + response generation. + """ + agent.register_hook(hookable_method="process_all_messages_before_reply", hook=self._transform_messages) + + def _transform_messages(self, messages: List[Dict]) -> List[Dict]: + post_transform_messages = copy.deepcopy(messages) + system_message = None + + if messages[0]["role"] == "system": + system_message = copy.deepcopy(messages[0]) + post_transform_messages.pop(0) + + for transform in self._transforms: + # deepcopy in case pre_transform_messages will later be used for logs printing + pre_transform_messages = ( + copy.deepcopy(post_transform_messages) if self._verbose else post_transform_messages + ) + post_transform_messages = transform.apply_transform(pre_transform_messages) + + if self._verbose: + logs_str, had_effect = transform.get_logs(pre_transform_messages, post_transform_messages) + if had_effect: + print(colored(logs_str, "yellow")) + + if system_message: + post_transform_messages.insert(0, system_message) + + return post_transform_messages diff --git a/autogen/agentchat/contrib/capabilities/transforms.py b/autogen/agentchat/contrib/capabilities/transforms.py new file mode 100644 index 00000000000..8303843e881 --- /dev/null +++ b/autogen/agentchat/contrib/capabilities/transforms.py @@ -0,0 +1,436 @@ +import copy +import json +import sys +from typing import Any, Dict, List, Optional, Protocol, Tuple, Union + +import tiktoken +from termcolor import colored + +from autogen import token_count_utils +from autogen.cache import AbstractCache, Cache + +from .text_compressors import LLMLingua, TextCompressor + + +class MessageTransform(Protocol): + """Defines a contract for message transformation. + + Classes implementing this protocol should provide an `apply_transform` method + that takes a list of messages and returns the transformed list. + """ + + def apply_transform(self, messages: List[Dict]) -> List[Dict]: + """Applies a transformation to a list of messages. + + Args: + messages: A list of dictionaries representing messages. + + Returns: + A new list of dictionaries containing the transformed messages. + """ + ... + + def get_logs(self, pre_transform_messages: List[Dict], post_transform_messages: List[Dict]) -> Tuple[str, bool]: + """Creates the string including the logs of the transformation + + Alongside the string, it returns a boolean indicating whether the transformation had an effect or not. + + Args: + pre_transform_messages: A list of dictionaries representing messages before the transformation. + post_transform_messages: A list of dictionaries representig messages after the transformation. + + Returns: + A tuple with a string with the logs and a flag indicating whether the transformation had an effect or not. + """ + ... + + +class MessageHistoryLimiter: + """Limits the number of messages considered by an agent for response generation. + + This transform keeps only the most recent messages up to the specified maximum number of messages (max_messages). + It trims the conversation history by removing older messages, retaining only the most recent messages. + """ + + def __init__(self, max_messages: Optional[int] = None): + """ + Args: + max_messages Optional[int]: Maximum number of messages to keep in the context. Must be greater than 0 if not None. + """ + self._validate_max_messages(max_messages) + self._max_messages = max_messages + + def apply_transform(self, messages: List[Dict]) -> List[Dict]: + """Truncates the conversation history to the specified maximum number of messages. + + This method returns a new list containing the most recent messages up to the specified + maximum number of messages (max_messages). If max_messages is None, it returns the + original list of messages unmodified. + + Args: + messages (List[Dict]): The list of messages representing the conversation history. + + Returns: + List[Dict]: A new list containing the most recent messages up to the specified maximum. + """ + + if self._max_messages is None: + return messages + + return messages[-self._max_messages :] + + def get_logs(self, pre_transform_messages: List[Dict], post_transform_messages: List[Dict]) -> Tuple[str, bool]: + pre_transform_messages_len = len(pre_transform_messages) + post_transform_messages_len = len(post_transform_messages) + + if post_transform_messages_len < pre_transform_messages_len: + logs_str = ( + f"Removed {pre_transform_messages_len - post_transform_messages_len} messages. " + f"Number of messages reduced from {pre_transform_messages_len} to {post_transform_messages_len}." + ) + return logs_str, True + return "No messages were removed.", False + + def _validate_max_messages(self, max_messages: Optional[int]): + if max_messages is not None and max_messages < 1: + raise ValueError("max_messages must be None or greater than 1") + + +class MessageTokenLimiter: + """Truncates messages to meet token limits for efficient processing and response generation. + + This transformation applies two levels of truncation to the conversation history: + + 1. Truncates each individual message to the maximum number of tokens specified by max_tokens_per_message. + 2. Truncates the overall conversation history to the maximum number of tokens specified by max_tokens. + + NOTE: Tokens are counted using the encoder for the specified model. Different models may yield different token + counts for the same text. + + NOTE: For multimodal LLMs, the token count may be inaccurate as it does not account for the non-text input + (e.g images). + + The truncation process follows these steps in order: + + 1. The minimum tokens threshold (`min_tokens`) is checked (0 by default). If the total number of tokens in messages + are less than this threshold, then the messages are returned as is. In other case, the following process is applied. + 2. Messages are processed in reverse order (newest to oldest). + 3. Individual messages are truncated based on max_tokens_per_message. For multimodal messages containing both text + and other types of content, only the text content is truncated. + 4. The overall conversation history is truncated based on the max_tokens limit. Once the accumulated token count + exceeds this limit, the current message being processed get truncated to meet the total token count and any + remaining messages get discarded. + 5. The truncated conversation history is reconstructed by prepending the messages to a new list to preserve the + original message order. + """ + + def __init__( + self, + max_tokens_per_message: Optional[int] = None, + max_tokens: Optional[int] = None, + min_tokens: Optional[int] = None, + model: str = "gpt-3.5-turbo-0613", + ): + """ + Args: + max_tokens_per_message (None or int): Maximum number of tokens to keep in each message. + Must be greater than or equal to 0 if not None. + max_tokens (Optional[int]): Maximum number of tokens to keep in the chat history. + Must be greater than or equal to 0 if not None. + min_tokens (Optional[int]): Minimum number of tokens in messages to apply the transformation. + Must be greater than or equal to 0 if not None. + model (str): The target OpenAI model for tokenization alignment. + """ + self._model = model + self._max_tokens_per_message = self._validate_max_tokens(max_tokens_per_message) + self._max_tokens = self._validate_max_tokens(max_tokens) + self._min_tokens = self._validate_min_tokens(min_tokens, max_tokens) + + def apply_transform(self, messages: List[Dict]) -> List[Dict]: + """Applies token truncation to the conversation history. + + Args: + messages (List[Dict]): The list of messages representing the conversation history. + + Returns: + List[Dict]: A new list containing the truncated messages up to the specified token limits. + """ + assert self._max_tokens_per_message is not None + assert self._max_tokens is not None + assert self._min_tokens is not None + + # if the total number of tokens in the messages is less than the min_tokens, return the messages as is + if not _min_tokens_reached(messages, self._min_tokens): + return messages + + temp_messages = copy.deepcopy(messages) + processed_messages = [] + processed_messages_tokens = 0 + + for msg in reversed(temp_messages): + # Some messages may not have content. + if not isinstance(msg.get("content"), (str, list)): + processed_messages.insert(0, msg) + continue + + expected_tokens_remained = self._max_tokens - processed_messages_tokens - self._max_tokens_per_message + + # If adding this message would exceed the token limit, truncate the last message to meet the total token + # limit and discard all remaining messages + if expected_tokens_remained < 0: + msg["content"] = self._truncate_str_to_tokens( + msg["content"], self._max_tokens - processed_messages_tokens + ) + processed_messages.insert(0, msg) + break + + msg["content"] = self._truncate_str_to_tokens(msg["content"], self._max_tokens_per_message) + msg_tokens = _count_tokens(msg["content"]) + + # prepend the message to the list to preserve order + processed_messages_tokens += msg_tokens + processed_messages.insert(0, msg) + + return processed_messages + + def get_logs(self, pre_transform_messages: List[Dict], post_transform_messages: List[Dict]) -> Tuple[str, bool]: + pre_transform_messages_tokens = sum( + _count_tokens(msg["content"]) for msg in pre_transform_messages if "content" in msg + ) + post_transform_messages_tokens = sum( + _count_tokens(msg["content"]) for msg in post_transform_messages if "content" in msg + ) + + if post_transform_messages_tokens < pre_transform_messages_tokens: + logs_str = ( + f"Truncated {pre_transform_messages_tokens - post_transform_messages_tokens} tokens. " + f"Number of tokens reduced from {pre_transform_messages_tokens} to {post_transform_messages_tokens}" + ) + return logs_str, True + return "No tokens were truncated.", False + + def _truncate_str_to_tokens(self, contents: Union[str, List], n_tokens: int) -> Union[str, List]: + if isinstance(contents, str): + return self._truncate_tokens(contents, n_tokens) + elif isinstance(contents, list): + return self._truncate_multimodal_text(contents, n_tokens) + else: + raise ValueError(f"Contents must be a string or a list of dictionaries. Received type: {type(contents)}") + + def _truncate_multimodal_text(self, contents: List[Dict[str, Any]], n_tokens: int) -> List[Dict[str, Any]]: + """Truncates text content within a list of multimodal elements, preserving the overall structure.""" + tmp_contents = [] + for content in contents: + if content["type"] == "text": + truncated_text = self._truncate_tokens(content["text"], n_tokens) + tmp_contents.append({"type": "text", "text": truncated_text}) + else: + tmp_contents.append(content) + return tmp_contents + + def _truncate_tokens(self, text: str, n_tokens: int) -> str: + encoding = tiktoken.encoding_for_model(self._model) # Get the appropriate tokenizer + + encoded_tokens = encoding.encode(text) + truncated_tokens = encoded_tokens[:n_tokens] + truncated_text = encoding.decode(truncated_tokens) # Decode back to text + + return truncated_text + + def _validate_max_tokens(self, max_tokens: Optional[int] = None) -> Optional[int]: + if max_tokens is not None and max_tokens < 0: + raise ValueError("max_tokens and max_tokens_per_message must be None or greater than or equal to 0") + + try: + allowed_tokens = token_count_utils.get_max_token_limit(self._model) + except Exception: + print(colored(f"Model {self._model} not found in token_count_utils.", "yellow")) + allowed_tokens = None + + if max_tokens is not None and allowed_tokens is not None: + if max_tokens > allowed_tokens: + print( + colored( + f"Max token was set to {max_tokens}, but {self._model} can only accept {allowed_tokens} tokens. Capping it to {allowed_tokens}.", + "yellow", + ) + ) + return allowed_tokens + + return max_tokens if max_tokens is not None else sys.maxsize + + def _validate_min_tokens(self, min_tokens: Optional[int], max_tokens: Optional[int]) -> int: + if min_tokens is None: + return 0 + if min_tokens < 0: + raise ValueError("min_tokens must be None or greater than or equal to 0.") + if max_tokens is not None and min_tokens > max_tokens: + raise ValueError("min_tokens must not be more than max_tokens.") + return min_tokens + + +class TextMessageCompressor: + """A transform for compressing text messages in a conversation history. + + It uses a specified text compression method to reduce the token count of messages, which can lead to more efficient + processing and response generation by downstream models. + """ + + def __init__( + self, + text_compressor: Optional[TextCompressor] = None, + min_tokens: Optional[int] = None, + compression_params: Dict = dict(), + cache: Optional[AbstractCache] = Cache.disk(), + ): + """ + Args: + text_compressor (TextCompressor or None): An instance of a class that implements the TextCompressor + protocol. If None, it defaults to LLMLingua. + min_tokens (int or None): Minimum number of tokens in messages to apply the transformation. Must be greater + than or equal to 0 if not None. If None, no threshold-based compression is applied. + compression_args (dict): A dictionary of arguments for the compression method. Defaults to an empty + dictionary. + cache (None or AbstractCache): The cache client to use to store and retrieve previously compressed messages. + If None, no caching will be used. + """ + + if text_compressor is None: + text_compressor = LLMLingua() + + self._validate_min_tokens(min_tokens) + + self._text_compressor = text_compressor + self._min_tokens = min_tokens + self._compression_args = compression_params + self._cache = cache + + # Optimizing savings calculations to optimize log generation + self._recent_tokens_savings = 0 + + def apply_transform(self, messages: List[Dict]) -> List[Dict]: + """Applies compression to messages in a conversation history based on the specified configuration. + + The function processes each message according to the `compression_args` and `min_tokens` settings, applying + the specified compression configuration and returning a new list of messages with reduced token counts + where possible. + + Args: + messages (List[Dict]): A list of message dictionaries to be compressed. + + Returns: + List[Dict]: A list of dictionaries with the message content compressed according to the configured + method and scope. + """ + # Make sure there is at least one message + if not messages: + return messages + + # if the total number of tokens in the messages is less than the min_tokens, return the messages as is + if not _min_tokens_reached(messages, self._min_tokens): + return messages + + total_savings = 0 + processed_messages = messages.copy() + for message in processed_messages: + # Some messages may not have content. + if not isinstance(message.get("content"), (str, list)): + continue + + if _is_content_text_empty(message["content"]): + continue + + cached_content = self._cache_get(message["content"]) + if cached_content is not None: + savings, compressed_content = cached_content + else: + savings, compressed_content = self._compress(message["content"]) + + self._cache_set(message["content"], compressed_content, savings) + + message["content"] = compressed_content + total_savings += savings + + self._recent_tokens_savings = total_savings + return processed_messages + + def get_logs(self, pre_transform_messages: List[Dict], post_transform_messages: List[Dict]) -> Tuple[str, bool]: + if self._recent_tokens_savings > 0: + return f"{self._recent_tokens_savings} tokens saved with text compression.", True + else: + return "No tokens saved with text compression.", False + + def _compress(self, content: Union[str, List[Dict]]) -> Tuple[int, Union[str, List[Dict]]]: + """Compresses the given text or multimodal content using the specified compression method.""" + if isinstance(content, str): + return self._compress_text(content) + elif isinstance(content, list): + return self._compress_multimodal(content) + else: + return 0, content + + def _compress_multimodal(self, content: List[Dict]) -> Tuple[int, List[Dict]]: + tokens_saved = 0 + for msg in content: + if "text" in msg: + savings, msg["text"] = self._compress_text(msg["text"]) + tokens_saved += savings + return tokens_saved, content + + def _compress_text(self, text: str) -> Tuple[int, str]: + """Compresses the given text using the specified compression method.""" + compressed_text = self._text_compressor.compress_text(text, **self._compression_args) + + savings = 0 + if "origin_tokens" in compressed_text and "compressed_tokens" in compressed_text: + savings = compressed_text["origin_tokens"] - compressed_text["compressed_tokens"] + + return savings, compressed_text["compressed_prompt"] + + def _cache_get(self, content: Union[str, List[Dict]]) -> Optional[Tuple[int, Union[str, List[Dict]]]]: + if self._cache: + cached_value = self._cache.get(self._cache_key(content)) + if cached_value: + return cached_value + + def _cache_set( + self, content: Union[str, List[Dict]], compressed_content: Union[str, List[Dict]], tokens_saved: int + ): + if self._cache: + value = (tokens_saved, json.dumps(compressed_content)) + self._cache.set(self._cache_key(content), value) + + def _cache_key(self, content: Union[str, List[Dict]]) -> str: + return f"{json.dumps(content)}_{self._min_tokens}" + + def _validate_min_tokens(self, min_tokens: Optional[int]): + if min_tokens is not None and min_tokens <= 0: + raise ValueError("min_tokens must be greater than 0 or None") + + +def _min_tokens_reached(messages: List[Dict], min_tokens: Optional[int]) -> bool: + """Returns True if the total number of tokens in the messages is greater than or equal to the specified value.""" + if not min_tokens: + return True + + messages_tokens = sum(_count_tokens(msg["content"]) for msg in messages if "content" in msg) + return messages_tokens >= min_tokens + + +def _count_tokens(content: Union[str, List[Dict[str, Any]]]) -> int: + token_count = 0 + if isinstance(content, str): + token_count = token_count_utils.count_token(content) + elif isinstance(content, list): + for item in content: + token_count += _count_tokens(item.get("text", "")) + return token_count + + +def _is_content_text_empty(content: Union[str, List[Dict[str, Any]]]) -> bool: + if isinstance(content, str): + return content == "" + elif isinstance(content, list): + return all(_is_content_text_empty(item.get("text", "")) for item in content) + else: + return False diff --git a/autogen/agentchat/contrib/compressible_agent.py b/autogen/agentchat/contrib/compressible_agent.py index 152cc871a56..9c4e78af852 100644 --- a/autogen/agentchat/contrib/compressible_agent.py +++ b/autogen/agentchat/contrib/compressible_agent.py @@ -1,20 +1,28 @@ -from typing import Callable, Dict, Optional, Union, Tuple, List, Any -from autogen import OpenAIWrapper -from autogen import Agent, ConversableAgent -import copy import asyncio -import logging +import copy import inspect +import logging +from typing import Any, Callable, Dict, List, Optional, Tuple, Union +from warnings import warn + +from autogen import Agent, ConversableAgent, OpenAIWrapper from autogen.token_count_utils import count_token, get_max_token_limit, num_tokens_from_functions from ...formatting_utils import colored logger = logging.getLogger(__name__) +warn( + "Context handling with CompressibleAgent is deprecated. " + "Please use `TransformMessages`, documentation can be found at https://microsoft.github.io/autogen/docs/reference/agentchat/contrib/capabilities/transform_messages", + DeprecationWarning, + stacklevel=2, +) + class CompressibleAgent(ConversableAgent): - """(CompressibleAgent will be deprecated. Refer to https://github.com/microsoft/autogen/blob/main/notebook/agentchat_capability_long_context_handling.ipynb for long context handling capability.) CompressibleAgent agent. While this agent retains all the default functionalities of the `AssistantAgent`, - it also provides the added feature of compression when activated through the `compress_config` setting. + """CompressibleAgent agent. While this agent retains all the default functionalities of the `AssistantAgent`, + it also provides the added feature of compression when activated through the `compress_config` setting. `compress_config` is set to False by default, making this agent equivalent to the `AssistantAgent`. This agent does not work well in a GroupChat: The compressed messages will not be sent to all the agents in the group. diff --git a/autogen/agentchat/contrib/gpt_assistant_agent.py b/autogen/agentchat/contrib/gpt_assistant_agent.py index 20acd2b08f8..0f5de8adcb5 100644 --- a/autogen/agentchat/contrib/gpt_assistant_agent.py +++ b/autogen/agentchat/contrib/gpt_assistant_agent.py @@ -1,16 +1,16 @@ -from collections import defaultdict -import openai +import copy import json -import time import logging -import copy +import time +from collections import defaultdict +from typing import Any, Dict, List, Optional, Tuple, Union + +import openai from autogen import OpenAIWrapper -from autogen.oai.openai_utils import retrieve_assistants_by_name from autogen.agentchat.agent import Agent -from autogen.agentchat.assistant_agent import ConversableAgent -from autogen.agentchat.assistant_agent import AssistantAgent -from typing import Dict, Optional, Union, List, Tuple, Any +from autogen.agentchat.assistant_agent import AssistantAgent, ConversableAgent +from autogen.oai.openai_utils import create_gpt_assistant, retrieve_assistants_by_name, update_gpt_assistant logger = logging.getLogger(__name__) @@ -50,7 +50,8 @@ def __init__( - check_every_ms: check thread run status interval - tools: Give Assistants access to OpenAI-hosted tools like Code Interpreter and Knowledge Retrieval, or build your own tools using Function calling. ref https://platform.openai.com/docs/assistants/tools - - file_ids: files used by retrieval in run + - file_ids: (Deprecated) files used by retrieval in run. It is Deprecated, use tool_resources instead. https://platform.openai.com/docs/assistants/migration/what-has-changed. + - tool_resources: A set of resources that are used by the assistant's tools. The resources are specific to the type of tool. overwrite_instructions (bool): whether to overwrite the instructions of an existing assistant. This parameter is in effect only when assistant_id is specified in llm_config. overwrite_tools (bool): whether to overwrite the tools of an existing assistant. This parameter is in effect only when assistant_id is specified in llm_config. kwargs (dict): Additional configuration options for the agent. @@ -90,7 +91,6 @@ def __init__( candidate_assistants, instructions, openai_assistant_cfg.get("tools", []), - openai_assistant_cfg.get("file_ids", []), ) if len(candidate_assistants) == 0: @@ -101,12 +101,12 @@ def __init__( "No instructions were provided for new assistant. Using default instructions from AssistantAgent.DEFAULT_SYSTEM_MESSAGE." ) instructions = AssistantAgent.DEFAULT_SYSTEM_MESSAGE - self._openai_assistant = self._openai_client.beta.assistants.create( + self._openai_assistant = create_gpt_assistant( + self._openai_client, name=name, instructions=instructions, - tools=openai_assistant_cfg.get("tools", []), model=model_name, - file_ids=openai_assistant_cfg.get("file_ids", []), + assistant_config=openai_assistant_cfg, ) else: logger.warning( @@ -127,9 +127,12 @@ def __init__( logger.warning( "overwrite_instructions is True. Provided instructions will be used and will modify the assistant in the API" ) - self._openai_assistant = self._openai_client.beta.assistants.update( + self._openai_assistant = update_gpt_assistant( + self._openai_client, assistant_id=openai_assistant_id, - instructions=instructions, + assistant_config={ + "instructions": instructions, + }, ) else: logger.warning( @@ -154,9 +157,13 @@ def __init__( logger.warning( "overwrite_tools is True. Provided tools will be used and will modify the assistant in the API" ) - self._openai_assistant = self._openai_client.beta.assistants.update( + self._openai_assistant = update_gpt_assistant( + self._openai_client, assistant_id=openai_assistant_id, - tools=openai_assistant_cfg.get("tools", []), + assistant_config={ + "tools": specified_tools, + "tool_resources": openai_assistant_cfg.get("tool_resources", None), + }, ) else: # Tools are specified but overwrite_tools is False; do not update the assistant's tools @@ -198,6 +205,8 @@ def _invoke_assistant( assistant_thread = self._openai_threads[sender] # Process each unread message for message in pending_messages: + if message["content"].strip() == "": + continue self._openai_client.beta.threads.messages.create( thread_id=assistant_thread.id, content=message["content"], @@ -426,22 +435,23 @@ def delete_assistant(self): logger.warning("Permanently deleting assistant...") self._openai_client.beta.assistants.delete(self.assistant_id) - def find_matching_assistant(self, candidate_assistants, instructions, tools, file_ids): + def find_matching_assistant(self, candidate_assistants, instructions, tools): """ Find the matching assistant from a list of candidate assistants. - Filter out candidates with the same name but different instructions, file IDs, and function names. - TODO: implement accurate match based on assistant metadata fields. + Filter out candidates with the same name but different instructions, and function names. """ matching_assistants = [] # Preprocess the required tools for faster comparison - required_tool_types = set(tool.get("type") for tool in tools) + required_tool_types = set( + "file_search" if tool.get("type") in ["retrieval", "file_search"] else tool.get("type") for tool in tools + ) + required_function_names = set( tool.get("function", {}).get("name") for tool in tools - if tool.get("type") not in ["code_interpreter", "retrieval"] + if tool.get("type") not in ["code_interpreter", "retrieval", "file_search"] ) - required_file_ids = set(file_ids) # Convert file_ids to a set for unordered comparison for assistant in candidate_assistants: # Check if instructions are similar @@ -454,11 +464,12 @@ def find_matching_assistant(self, candidate_assistants, instructions, tools, fil continue # Preprocess the assistant's tools - assistant_tool_types = set(tool.type for tool in assistant.tools) + assistant_tool_types = set( + "file_search" if tool.type in ["retrieval", "file_search"] else tool.type for tool in assistant.tools + ) assistant_function_names = set(tool.function.name for tool in assistant.tools if hasattr(tool, "function")) - assistant_file_ids = set(getattr(assistant, "file_ids", [])) # Convert to set for comparison - # Check if the tool types, function names, and file IDs match + # Check if the tool types, function names match if required_tool_types != assistant_tool_types or required_function_names != assistant_function_names: logger.warning( "tools not match, skip assistant(%s): tools %s, functions %s", @@ -467,9 +478,6 @@ def find_matching_assistant(self, candidate_assistants, instructions, tools, fil assistant_function_names, ) continue - if required_file_ids != assistant_file_ids: - logger.warning("file_ids not match, skip assistant(%s): %s", assistant.id, assistant_file_ids) - continue # Append assistant to matching list if all conditions are met matching_assistants.append(assistant) @@ -496,7 +504,7 @@ def _process_assistant_config(self, llm_config, assistant_config): # Move the assistant related configurations to assistant_config # It's important to keep forward compatibility - assistant_config_items = ["assistant_id", "tools", "file_ids", "check_every_ms"] + assistant_config_items = ["assistant_id", "tools", "file_ids", "tool_resources", "check_every_ms"] for item in assistant_config_items: if openai_client_cfg.get(item) is not None and openai_assistant_cfg.get(item) is None: openai_assistant_cfg[item] = openai_client_cfg[item] diff --git a/autogen/agentchat/contrib/llava_agent.py b/autogen/agentchat/contrib/llava_agent.py index 182f72837b7..063b256d3cd 100644 --- a/autogen/agentchat/contrib/llava_agent.py +++ b/autogen/agentchat/contrib/llava_agent.py @@ -1,6 +1,7 @@ import json import logging from typing import List, Optional, Tuple + import replicate import requests @@ -8,8 +9,8 @@ from autogen.agentchat.contrib.img_utils import get_image_data, llava_formatter from autogen.agentchat.contrib.multimodal_conversable_agent import MultimodalConversableAgent from autogen.code_utils import content_str -from ...formatting_utils import colored +from ...formatting_utils import colored logger = logging.getLogger(__name__) diff --git a/autogen/agentchat/contrib/math_user_proxy_agent.py b/autogen/agentchat/contrib/math_user_proxy_agent.py index 70f365ef9fe..d2b6b7cde00 100644 --- a/autogen/agentchat/contrib/math_user_proxy_agent.py +++ b/autogen/agentchat/contrib/math_user_proxy_agent.py @@ -1,15 +1,15 @@ -import re import os -from pydantic import BaseModel, Extra, root_validator -from typing import Any, Callable, Dict, List, Optional, Union, Tuple +import re from time import sleep +from typing import Any, Callable, Dict, List, Optional, Tuple, Union + +from pydantic import BaseModel, Extra, root_validator from autogen._pydantic import PYDANTIC_V1 from autogen.agentchat import Agent, UserProxyAgent -from autogen.code_utils import UNKNOWN, extract_code, execute_code, infer_lang +from autogen.code_utils import UNKNOWN, execute_code, extract_code, infer_lang from autogen.math_utils import get_answer - PROMPTS = { # default "default": """Let's use Python to solve a math problem. diff --git a/autogen/agentchat/contrib/multimodal_conversable_agent.py b/autogen/agentchat/contrib/multimodal_conversable_agent.py index 2a016bcffba..edeb88cd531 100644 --- a/autogen/agentchat/contrib/multimodal_conversable_agent.py +++ b/autogen/agentchat/contrib/multimodal_conversable_agent.py @@ -11,7 +11,6 @@ from ..._pydantic import model_dump - DEFAULT_LMM_SYS_MSG = """You are a helpful AI assistant.""" DEFAULT_MODEL = "gpt-4-vision-preview" diff --git a/autogen/agentchat/contrib/qdrant_retrieve_user_proxy_agent.py b/autogen/agentchat/contrib/qdrant_retrieve_user_proxy_agent.py index c539c716ab8..1ece138963f 100644 --- a/autogen/agentchat/contrib/qdrant_retrieve_user_proxy_agent.py +++ b/autogen/agentchat/contrib/qdrant_retrieve_user_proxy_agent.py @@ -1,17 +1,21 @@ from typing import Callable, Dict, List, Optional from autogen.agentchat.contrib.retrieve_user_proxy_agent import RetrieveUserProxyAgent -from autogen.retrieve_utils import get_files_from_dir, split_files_to_chunks, TEXT_FORMATS -import logging +from autogen.agentchat.contrib.vectordb.utils import ( + chroma_results_to_query_results, + filter_results_by_distance, + get_logger, +) +from autogen.retrieve_utils import TEXT_FORMATS, get_files_from_dir, split_files_to_chunks -logger = logging.getLogger(__name__) +logger = get_logger(__name__) try: + import fastembed from qdrant_client import QdrantClient, models from qdrant_client.fastembed_common import QueryResponse - import fastembed except ImportError as e: - logging.fatal("Failed to import qdrant_client with fastembed. Try running 'pip install qdrant_client[fastembed]'") + logger.fatal("Failed to import qdrant_client with fastembed. Try running 'pip install qdrant_client[fastembed]'") raise e @@ -136,6 +140,11 @@ def retrieve_docs(self, problem: str, n_results: int = 20, search_string: str = collection_name=self._collection_name, embedding_model=self._embedding_model, ) + results["contents"] = results.pop("documents") + results = chroma_results_to_query_results(results, "distances") + results = filter_results_by_distance(results, self._distance_threshold) + + self._search_string = search_string self._results = results @@ -190,12 +199,12 @@ def create_qdrant_from_dir( client.set_model(embedding_model) if custom_text_split_function is not None: - chunks = split_files_to_chunks( + chunks, sources = split_files_to_chunks( get_files_from_dir(dir_path, custom_text_types, recursive), custom_text_split_function=custom_text_split_function, ) else: - chunks = split_files_to_chunks( + chunks, sources = split_files_to_chunks( get_files_from_dir(dir_path, custom_text_types, recursive), max_tokens, chunk_mode, must_break_at_empty_line ) logger.info(f"Found {len(chunks)} chunks.") @@ -298,5 +307,7 @@ class QueryResponse(BaseModel, extra="forbid"): # type: ignore data = { "ids": [[result.id for result in sublist] for sublist in results], "documents": [[result.document for result in sublist] for sublist in results], + "distances": [[result.score for result in sublist] for sublist in results], + "metadatas": [[result.metadata for result in sublist] for sublist in results], } return data diff --git a/autogen/agentchat/contrib/retrieve_assistant_agent.py b/autogen/agentchat/contrib/retrieve_assistant_agent.py index a09677710aa..9b5ace200dc 100644 --- a/autogen/agentchat/contrib/retrieve_assistant_agent.py +++ b/autogen/agentchat/contrib/retrieve_assistant_agent.py @@ -1,6 +1,7 @@ +from typing import Any, Dict, List, Optional, Tuple, Union + from autogen.agentchat.agent import Agent from autogen.agentchat.assistant_agent import AssistantAgent -from typing import Dict, Optional, Union, List, Tuple, Any class RetrieveAssistantAgent(AssistantAgent): diff --git a/autogen/agentchat/contrib/retrieve_user_proxy_agent.py b/autogen/agentchat/contrib/retrieve_user_proxy_agent.py index f252f60e5ec..476c7c0739d 100644 --- a/autogen/agentchat/contrib/retrieve_user_proxy_agent.py +++ b/autogen/agentchat/contrib/retrieve_user_proxy_agent.py @@ -1,19 +1,35 @@ +import hashlib +import os import re -from typing import Callable, Dict, Optional, Union, List, Tuple, Any +from typing import Any, Callable, Dict, List, Optional, Tuple, Union + from IPython import get_ipython try: import chromadb except ImportError: raise ImportError("Please install dependencies first. `pip install pyautogen[retrievechat]`") -from autogen.agentchat.agent import Agent from autogen.agentchat import UserProxyAgent -from autogen.retrieve_utils import create_vector_db_from_dir, query_vector_db, TEXT_FORMATS -from autogen.token_count_utils import count_token +from autogen.agentchat.agent import Agent +from autogen.agentchat.contrib.vectordb.base import Document, QueryResults, VectorDB, VectorDBFactory +from autogen.agentchat.contrib.vectordb.utils import ( + chroma_results_to_query_results, + filter_results_by_distance, + get_logger, +) from autogen.code_utils import extract_code -from autogen import logger +from autogen.retrieve_utils import ( + TEXT_FORMATS, + create_vector_db_from_dir, + get_files_from_dir, + query_vector_db, + split_files_to_chunks, +) +from autogen.token_count_utils import count_token + from ...formatting_utils import colored +logger = get_logger(__name__) PROMPT_DEFAULT = """You're a retrieve augmented chatbot. You answer user's questions based on your own knowledge and the context provided by the user. You should follow the following steps to answer a question: @@ -33,6 +49,10 @@ User's question is: {input_question} Context is: {input_context} + +The source of the context is: {input_sources} + +If you can answer the question, in the end of your answer, add the source of the context in the format of `Sources: source1, source2, ...`. """ PROMPT_CODE = """You're a retrieve augmented coding assistant. You answer user's questions based on your own knowledge and the @@ -60,8 +80,14 @@ Context is: {input_context} """ +HASH_LENGTH = int(os.environ.get("HASH_LENGTH", 8)) + class RetrieveUserProxyAgent(UserProxyAgent): + """(In preview) The Retrieval-Augmented User Proxy retrieves document chunks based on the embedding + similarity, and sends them along with the question to the Retrieval-Augmented Assistant + """ + def __init__( self, name="RetrieveChatAgent", # default set to RetrieveChatAgent @@ -73,67 +99,126 @@ def __init__( r""" Args: name (str): name of the agent. + human_input_mode (str): whether to ask for human inputs every time a message is received. Possible values are "ALWAYS", "TERMINATE", "NEVER". 1. When "ALWAYS", the agent prompts for human input every time a message is received. Under this mode, the conversation stops when the human input is "exit", or when is_termination_msg is True and there is no human input. - 2. When "TERMINATE", the agent only prompts for human input only when a termination message is received or - the number of auto reply reaches the max_consecutive_auto_reply. - 3. When "NEVER", the agent will never prompt for human input. Under this mode, the conversation stops - when the number of auto reply reaches the max_consecutive_auto_reply or when is_termination_msg is True. + 2. When "TERMINATE", the agent only prompts for human input only when a termination + message is received or the number of auto reply reaches + the max_consecutive_auto_reply. + 3. When "NEVER", the agent will never prompt for human input. Under this mode, the + conversation stops when the number of auto reply reaches the + max_consecutive_auto_reply or when is_termination_msg is True. + is_termination_msg (function): a function that takes a message in the form of a dictionary and returns a boolean value indicating if this received message is a termination message. The dict can contain the following keys: "content", "role", "name", "function_call". + retrieve_config (dict or None): config for the retrieve agent. - To use default config, set to None. Otherwise, set to a dictionary with the following keys: - - task (Optional, str): the task of the retrieve chat. Possible values are "code", "qa" and "default". System - prompt will be different for different tasks. The default value is `default`, which supports both code and qa. - - client (Optional, chromadb.Client): the chromadb client. If key not provided, a default client `chromadb.Client()` - will be used. If you want to use other vector db, extend this class and override the `retrieve_docs` function. - - docs_path (Optional, Union[str, List[str]]): the path to the docs directory. It can also be the path to a single file, - the url to a single file or a list of directories, files and urls. Default is None, which works only if the collection is already created. - - extra_docs (Optional, bool): when true, allows adding documents with unique IDs without overwriting existing ones; when false, it replaces existing documents using default IDs, risking collection overwrite., - when set to true it enables the system to assign unique IDs starting from "length+i" for new document chunks, preventing the replacement of existing documents and facilitating the addition of more content to the collection.. - By default, "extra_docs" is set to false, starting document IDs from zero. This poses a risk as new documents might overwrite existing ones, potentially causing unintended loss or alteration of data in the collection. - - collection_name (Optional, str): the name of the collection. - If key not provided, a default name `autogen-docs` will be used. - - model (Optional, str): the model to use for the retrieve chat. + + To use default config, set to None. Otherwise, set to a dictionary with the + following keys: + - `task` (Optional, str) - the task of the retrieve chat. Possible values are + "code", "qa" and "default". System prompt will be different for different tasks. + The default value is `default`, which supports both code and qa, and provides + source information in the end of the response. + - `vector_db` (Optional, Union[str, VectorDB]) - the vector db for the retrieve chat. + If it's a string, it should be the type of the vector db, such as "chroma"; otherwise, + it should be an instance of the VectorDB protocol. Default is "chroma". + Set `None` to use the deprecated `client`. + - `db_config` (Optional, Dict) - the config for the vector db. Default is `{}`. Please make + sure you understand the config for the vector db you are using, otherwise, leave it as `{}`. + Only valid when `vector_db` is a string. + - `client` (Optional, chromadb.Client) - the chromadb client. If key not provided, a + default client `chromadb.Client()` will be used. If you want to use other + vector db, extend this class and override the `retrieve_docs` function. + **Deprecated**: use `vector_db` instead. + - `docs_path` (Optional, Union[str, List[str]]) - the path to the docs directory. It + can also be the path to a single file, the url to a single file or a list + of directories, files and urls. Default is None, which works only if the + collection is already created. + - `extra_docs` (Optional, bool) - when true, allows adding documents with unique IDs + without overwriting existing ones; when false, it replaces existing documents + using default IDs, risking collection overwrite., when set to true it enables + the system to assign unique IDs starting from "length+i" for new document + chunks, preventing the replacement of existing documents and facilitating the + addition of more content to the collection.. + By default, "extra_docs" is set to false, starting document IDs from zero. + This poses a risk as new documents might overwrite existing ones, potentially + causing unintended loss or alteration of data in the collection. + **Deprecated**: use `new_docs` when use `vector_db` instead of `client`. + - `new_docs` (Optional, bool) - when True, only adds new documents to the collection; + when False, updates existing documents and adds new ones. Default is True. + Document id is used to determine if a document is new or existing. By default, the + id is the hash value of the content. + - `model` (Optional, str) - the model to use for the retrieve chat. If key not provided, a default model `gpt-4` will be used. - - chunk_token_size (Optional, int): the chunk token size for the retrieve chat. + - `chunk_token_size` (Optional, int) - the chunk token size for the retrieve chat. If key not provided, a default size `max_tokens * 0.4` will be used. - - context_max_tokens (Optional, int): the context max token size for the retrieve chat. + - `context_max_tokens` (Optional, int) - the context max token size for the + retrieve chat. If key not provided, a default size `max_tokens * 0.8` will be used. - - chunk_mode (Optional, str): the chunk mode for the retrieve chat. Possible values are - "multi_lines" and "one_line". If key not provided, a default mode `multi_lines` will be used. - - must_break_at_empty_line (Optional, bool): chunk will only break at empty line if True. Default is True. + - `chunk_mode` (Optional, str) - the chunk mode for the retrieve chat. Possible values + are "multi_lines" and "one_line". If key not provided, a default mode + `multi_lines` will be used. + - `must_break_at_empty_line` (Optional, bool) - chunk will only break at empty line + if True. Default is True. If chunk_mode is "one_line", this parameter will be ignored. - - embedding_model (Optional, str): the embedding model to use for the retrieve chat. - If key not provided, a default model `all-MiniLM-L6-v2` will be used. All available models - can be found at `https://www.sbert.net/docs/pretrained_models.html`. The default model is a - fast model. If you want to use a high performance model, `all-mpnet-base-v2` is recommended. - - embedding_function (Optional, Callable): the embedding function for creating the vector db. Default is None, - SentenceTransformer with the given `embedding_model` will be used. If you want to use OpenAI, Cohere, HuggingFace or - other embedding functions, you can pass it here, follow the examples in `https://docs.trychroma.com/embeddings`. - - customized_prompt (Optional, str): the customized prompt for the retrieve chat. Default is None. - - customized_answer_prefix (Optional, str): the customized answer prefix for the retrieve chat. Default is "". - If not "" and the customized_answer_prefix is not in the answer, `Update Context` will be triggered. - - update_context (Optional, bool): if False, will not apply `Update Context` for interactive retrieval. Default is True. - - get_or_create (Optional, bool): if True, will create/return a collection for the retrieve chat. This is the same as that used in chromadb. - Default is False. Will raise ValueError if the collection already exists and get_or_create is False. Will be set to True if docs_path is None. - - custom_token_count_function (Optional, Callable): a custom function to count the number of tokens in a string. - The function should take (text:str, model:str) as input and return the token_count(int). the retrieve_config["model"] will be passed in the function. - Default is autogen.token_count_utils.count_token that uses tiktoken, which may not be accurate for non-OpenAI models. - - custom_text_split_function (Optional, Callable): a custom function to split a string into a list of strings. - Default is None, will use the default function in `autogen.retrieve_utils.split_text_to_chunks`. - - custom_text_types (Optional, List[str]): a list of file types to be processed. Default is `autogen.retrieve_utils.TEXT_FORMATS`. - This only applies to files under the directories in `docs_path`. Explicitly included files and urls will be chunked regardless of their types. - - recursive (Optional, bool): whether to search documents recursively in the docs_path. Default is True. + - `embedding_model` (Optional, str) - the embedding model to use for the retrieve chat. + If key not provided, a default model `all-MiniLM-L6-v2` will be used. All available + models can be found at `https://www.sbert.net/docs/pretrained_models.html`. + The default model is a fast model. If you want to use a high performance model, + `all-mpnet-base-v2` is recommended. + **Deprecated**: no need when use `vector_db` instead of `client`. + - `embedding_function` (Optional, Callable) - the embedding function for creating the + vector db. Default is None, SentenceTransformer with the given `embedding_model` + will be used. If you want to use OpenAI, Cohere, HuggingFace or other embedding + functions, you can pass it here, + follow the examples in `https://docs.trychroma.com/embeddings`. + - `customized_prompt` (Optional, str) - the customized prompt for the retrieve chat. + Default is None. + - `customized_answer_prefix` (Optional, str) - the customized answer prefix for the + retrieve chat. Default is "". + If not "" and the customized_answer_prefix is not in the answer, + `Update Context` will be triggered. + - `update_context` (Optional, bool) - if False, will not apply `Update Context` for + interactive retrieval. Default is True. + - `collection_name` (Optional, str) - the name of the collection. + If key not provided, a default name `autogen-docs` will be used. + - `get_or_create` (Optional, bool) - Whether to get the collection if it exists. Default is True. + - `overwrite` (Optional, bool) - Whether to overwrite the collection if it exists. Default is False. + Case 1. if the collection does not exist, create the collection. + Case 2. the collection exists, if overwrite is True, it will overwrite the collection. + Case 3. the collection exists and overwrite is False, if get_or_create is True, it will get the collection, + otherwise it raise a ValueError. + - `custom_token_count_function` (Optional, Callable) - a custom function to count the + number of tokens in a string. + The function should take (text:str, model:str) as input and return the + token_count(int). the retrieve_config["model"] will be passed in the function. + Default is autogen.token_count_utils.count_token that uses tiktoken, which may + not be accurate for non-OpenAI models. + - `custom_text_split_function` (Optional, Callable) - a custom function to split a + string into a list of strings. + Default is None, will use the default function in + `autogen.retrieve_utils.split_text_to_chunks`. + - `custom_text_types` (Optional, List[str]) - a list of file types to be processed. + Default is `autogen.retrieve_utils.TEXT_FORMATS`. + This only applies to files under the directories in `docs_path`. Explicitly + included files and urls will be chunked regardless of their types. + - `recursive` (Optional, bool) - whether to search documents recursively in the + docs_path. Default is True. + - `distance_threshold` (Optional, float) - the threshold for the distance score, only + distance smaller than it will be returned. Will be ignored if < 0. Default is -1. + `**kwargs` (dict): other kwargs in [UserProxyAgent](../user_proxy_agent#__init__). Example: - Example of overriding retrieve_docs - If you have set up a customized vector db, and it's not compatible with chromadb, you can easily plug in it with below code. + Example of overriding retrieve_docs - If you have set up a customized vector db, and it's + not compatible with chromadb, you can easily plug in it with below code. + **Deprecated**: Use `vector_db` instead. You can extend VectorDB and pass it to the agent. ```python class MyRetrieveUserProxyAgent(RetrieveUserProxyAgent): def query_vector_db( @@ -166,9 +251,12 @@ def retrieve_docs(self, problem: str, n_results: int = 20, search_string: str = self._retrieve_config = {} if retrieve_config is None else retrieve_config self._task = self._retrieve_config.get("task", "default") + self._vector_db = self._retrieve_config.get("vector_db", "chroma") + self._db_config = self._retrieve_config.get("db_config", {}) self._client = self._retrieve_config.get("client", chromadb.Client()) self._docs_path = self._retrieve_config.get("docs_path", None) self._extra_docs = self._retrieve_config.get("extra_docs", False) + self._new_docs = self._retrieve_config.get("new_docs", True) self._collection_name = self._retrieve_config.get("collection_name", "autogen-docs") if "docs_path" not in self._retrieve_config: logger.warning( @@ -187,25 +275,104 @@ def retrieve_docs(self, problem: str, n_results: int = 20, search_string: str = self.customized_answer_prefix = self._retrieve_config.get("customized_answer_prefix", "").upper() self.update_context = self._retrieve_config.get("update_context", True) self._get_or_create = self._retrieve_config.get("get_or_create", False) if self._docs_path is not None else True + self._overwrite = self._retrieve_config.get("overwrite", False) self.custom_token_count_function = self._retrieve_config.get("custom_token_count_function", count_token) self.custom_text_split_function = self._retrieve_config.get("custom_text_split_function", None) self._custom_text_types = self._retrieve_config.get("custom_text_types", TEXT_FORMATS) self._recursive = self._retrieve_config.get("recursive", True) - self._context_max_tokens = self._max_tokens * 0.8 + self._context_max_tokens = self._retrieve_config.get("context_max_tokens", self._max_tokens * 0.8) self._collection = True if self._docs_path is None else False # whether the collection is created self._ipython = get_ipython() self._doc_idx = -1 # the index of the current used doc - self._results = {} # the results of the current query + self._results = [] # the results of the current query self._intermediate_answers = set() # the intermediate answers self._doc_contents = [] # the contents of the current used doc self._doc_ids = [] # the ids of the current used doc + self._current_docs_in_context = [] # the ids of the current context sources self._search_string = "" # the search string used in the current query + self._distance_threshold = self._retrieve_config.get("distance_threshold", -1) # update the termination message function self._is_termination_msg = ( self._is_termination_msg_retrievechat if is_termination_msg is None else is_termination_msg ) + if isinstance(self._vector_db, str): + if not isinstance(self._db_config, dict): + raise ValueError("`db_config` should be a dictionary.") + if "embedding_function" in self._retrieve_config: + self._db_config["embedding_function"] = self._embedding_function + self._vector_db = VectorDBFactory.create_vector_db(db_type=self._vector_db, **self._db_config) self.register_reply(Agent, RetrieveUserProxyAgent._generate_retrieve_user_reply, position=2) + def _init_db(self): + if not self._vector_db: + return + + IS_TO_CHUNK = False # whether to chunk the raw files + if self._new_docs: + IS_TO_CHUNK = True + if not self._docs_path: + try: + self._vector_db.get_collection(self._collection_name) + logger.warning(f"`docs_path` is not provided. Use the existing collection `{self._collection_name}`.") + self._overwrite = False + self._get_or_create = True + IS_TO_CHUNK = False + except ValueError: + raise ValueError( + "`docs_path` is not provided. " + f"The collection `{self._collection_name}` doesn't exist either. " + "Please provide `docs_path` or create the collection first." + ) + elif self._get_or_create and not self._overwrite: + try: + self._vector_db.get_collection(self._collection_name) + logger.info(f"Use the existing collection `{self._collection_name}`.", color="green") + except ValueError: + IS_TO_CHUNK = True + else: + IS_TO_CHUNK = True + + self._vector_db.active_collection = self._vector_db.create_collection( + self._collection_name, overwrite=self._overwrite, get_or_create=self._get_or_create + ) + + docs = None + if IS_TO_CHUNK: + if self.custom_text_split_function is not None: + chunks, sources = split_files_to_chunks( + get_files_from_dir(self._docs_path, self._custom_text_types, self._recursive), + custom_text_split_function=self.custom_text_split_function, + ) + else: + chunks, sources = split_files_to_chunks( + get_files_from_dir(self._docs_path, self._custom_text_types, self._recursive), + self._max_tokens, + self._chunk_mode, + self._must_break_at_empty_line, + ) + logger.info(f"Found {len(chunks)} chunks.") + + if self._new_docs: + all_docs_ids = set( + [ + doc["id"] + for doc in self._vector_db.get_docs_by_ids(ids=None, collection_name=self._collection_name) + ] + ) + else: + all_docs_ids = set() + + chunk_ids = [hashlib.blake2b(chunk.encode("utf-8")).hexdigest()[:HASH_LENGTH] for chunk in chunks] + chunk_ids_set = set(chunk_ids) + chunk_ids_set_idx = [chunk_ids.index(hash_value) for hash_value in chunk_ids_set] + docs = [ + Document(id=chunk_ids[idx], content=chunks[idx], metadata=sources[idx]) + for idx in chunk_ids_set_idx + if chunk_ids[idx] not in all_docs_ids + ] + + self._vector_db.insert_docs(docs=docs, collection_name=self._collection_name, upsert=True) + def _is_termination_msg_retrievechat(self, message): """Check if a message is a termination message. For code generation, terminate when no code block is detected. Currently only detect python code blocks. @@ -238,37 +405,42 @@ def get_max_tokens(model="gpt-3.5-turbo"): def _reset(self, intermediate=False): self._doc_idx = -1 # the index of the current used doc - self._results = {} # the results of the current query + self._results = [] # the results of the current query if not intermediate: self._intermediate_answers = set() # the intermediate answers self._doc_contents = [] # the contents of the current used doc self._doc_ids = [] # the ids of the current used doc - def _get_context(self, results: Dict[str, Union[List[str], List[List[str]]]]): + def _get_context(self, results: QueryResults): doc_contents = "" + self._current_docs_in_context = [] current_tokens = 0 _doc_idx = self._doc_idx _tmp_retrieve_count = 0 - for idx, doc in enumerate(results["documents"][0]): + for idx, doc in enumerate(results[0]): + doc = doc[0] if idx <= _doc_idx: continue - if results["ids"][0][idx] in self._doc_ids: + if doc["id"] in self._doc_ids: continue - _doc_tokens = self.custom_token_count_function(doc, self._model) + _doc_tokens = self.custom_token_count_function(doc["content"], self._model) if _doc_tokens > self._context_max_tokens: - func_print = f"Skip doc_id {results['ids'][0][idx]} as it is too long to fit in the context." + func_print = f"Skip doc_id {doc['id']} as it is too long to fit in the context." print(colored(func_print, "green"), flush=True) self._doc_idx = idx continue if current_tokens + _doc_tokens > self._context_max_tokens: break - func_print = f"Adding doc_id {results['ids'][0][idx]} to context." + func_print = f"Adding content of doc {doc['id']} to context." print(colored(func_print, "green"), flush=True) current_tokens += _doc_tokens - doc_contents += doc + "\n" + doc_contents += doc["content"] + "\n" + _metadata = doc.get("metadata") + if isinstance(_metadata, dict): + self._current_docs_in_context.append(_metadata.get("source", "")) self._doc_idx = idx - self._doc_ids.append(results["ids"][0][idx]) - self._doc_contents.append(doc) + self._doc_ids.append(doc["id"]) + self._doc_contents.append(doc["content"]) _tmp_retrieve_count += 1 if _tmp_retrieve_count >= self.n_results: break @@ -285,7 +457,9 @@ def _generate_message(self, doc_contents, task="default"): elif task.upper() == "QA": message = PROMPT_QA.format(input_question=self.problem, input_context=doc_contents) elif task.upper() == "DEFAULT": - message = PROMPT_DEFAULT.format(input_question=self.problem, input_context=doc_contents) + message = PROMPT_DEFAULT.format( + input_question=self.problem, input_context=doc_contents, input_sources=self._current_docs_in_context + ) else: raise NotImplementedError(f"task {task} is not implemented.") return message @@ -360,21 +534,40 @@ def _generate_retrieve_user_reply( def retrieve_docs(self, problem: str, n_results: int = 20, search_string: str = ""): """Retrieve docs based on the given problem and assign the results to the class property `_results`. - In case you want to customize the retrieval process, such as using a different vector db whose APIs are not - compatible with chromadb or filter results with metadata, you can override this function. Just keep the current - parameters and add your own parameters with default values, and keep the results in below type. - - Type of the results: Dict[str, List[List[Any]]], should have keys "ids" and "documents", "ids" for the ids of - the retrieved docs and "documents" for the contents of the retrieved docs. Any other keys are optional. Refer - to `chromadb.api.types.QueryResult` as an example. - ids: List[string] - documents: List[List[string]] + The retrieved docs should be type of `QueryResults` which is a list of tuples containing the document and + the distance. Args: problem (str): the problem to be solved. n_results (int): the number of results to be retrieved. Default is 20. search_string (str): only docs that contain an exact match of this string will be retrieved. Default is "". + Not used if the vector_db doesn't support it. + + Returns: + None. """ + if isinstance(self._vector_db, VectorDB): + if not self._collection or not self._get_or_create: + print("Trying to create collection.") + self._init_db() + self._collection = True + self._get_or_create = True + + kwargs = {} + if hasattr(self._vector_db, "type") and self._vector_db.type == "chroma": + kwargs["where_document"] = {"$contains": search_string} if search_string else None + results = self._vector_db.retrieve_docs( + queries=[problem], + n_results=n_results, + collection_name=self._collection_name, + distance_threshold=self._distance_threshold, + **kwargs, + ) + self._search_string = search_string + self._results = results + print("VectorDB returns doc_ids: ", [[r[0]["id"] for r in rr] for rr in results]) + return + if not self._collection or not self._get_or_create: print("Trying to create collection.") self._client = create_vector_db_from_dir( @@ -404,9 +597,13 @@ def retrieve_docs(self, problem: str, n_results: int = 20, search_string: str = embedding_model=self._embedding_model, embedding_function=self._embedding_function, ) + results["contents"] = results.pop("documents") + results = chroma_results_to_query_results(results, "distances") + results = filter_results_by_distance(results, self._distance_threshold) + self._search_string = search_string self._results = results - print("doc_ids: ", results["ids"]) + print("doc_ids: ", [[r[0]["id"] for r in rr] for rr in results]) @staticmethod def message_generator(sender, recipient, context): @@ -416,9 +613,9 @@ def message_generator(sender, recipient, context): sender (Agent): the sender agent. It should be the instance of RetrieveUserProxyAgent. recipient (Agent): the recipient agent. Usually it's the assistant agent. context (dict): the context for the message generation. It should contain the following keys: - - problem (str): the problem to be solved. - - n_results (int): the number of results to be retrieved. Default is 20. - - search_string (str): only docs that contain an exact match of this string will be retrieved. Default is "". + - `problem` (str) - the problem to be solved. + - `n_results` (int) - the number of results to be retrieved. Default is 20. + - `search_string` (str) - only docs that contain an exact match of this string will be retrieved. Default is "". Returns: str: the generated message ready to be sent to the recipient agent. """ diff --git a/autogen/agentchat/contrib/society_of_mind_agent.py b/autogen/agentchat/contrib/society_of_mind_agent.py index 6a6f4aa2186..97cf6aee1a5 100644 --- a/autogen/agentchat/contrib/society_of_mind_agent.py +++ b/autogen/agentchat/contrib/society_of_mind_agent.py @@ -1,10 +1,11 @@ # ruff: noqa: E722 +import copy import json import traceback -import copy from dataclasses import dataclass -from typing import Dict, List, Optional, Union, Callable, Literal, Tuple -from autogen import Agent, ConversableAgent, GroupChatManager, GroupChat, OpenAIWrapper +from typing import Callable, Dict, List, Literal, Optional, Tuple, Union + +from autogen import Agent, ConversableAgent, GroupChat, GroupChatManager, OpenAIWrapper class SocietyOfMindAgent(ConversableAgent): diff --git a/autogen/agentchat/contrib/text_analyzer_agent.py b/autogen/agentchat/contrib/text_analyzer_agent.py index 10100c9e57f..e917cca574f 100644 --- a/autogen/agentchat/contrib/text_analyzer_agent.py +++ b/autogen/agentchat/contrib/text_analyzer_agent.py @@ -1,7 +1,8 @@ +from typing import Any, Callable, Dict, List, Optional, Tuple, Union + from autogen import oai from autogen.agentchat.agent import Agent from autogen.agentchat.assistant_agent import ConversableAgent -from typing import Callable, Dict, Optional, Union, List, Tuple, Any system_message = """You are an expert in text analysis. The user will give you TEXT to analyze. diff --git a/autogen/agentchat/contrib/vectordb/__init__.py b/autogen/agentchat/contrib/vectordb/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/autogen/agentchat/contrib/vectordb/base.py b/autogen/agentchat/contrib/vectordb/base.py new file mode 100644 index 00000000000..29a08008619 --- /dev/null +++ b/autogen/agentchat/contrib/vectordb/base.py @@ -0,0 +1,213 @@ +from typing import Any, List, Mapping, Optional, Protocol, Sequence, Tuple, TypedDict, Union, runtime_checkable + +Metadata = Union[Mapping[str, Any], None] +Vector = Union[Sequence[float], Sequence[int]] +ItemID = Union[str, int] # chromadb doesn't support int ids, VikingDB does + + +class Document(TypedDict): + """A Document is a record in the vector database. + + id: ItemID | the unique identifier of the document. + content: str | the text content of the chunk. + metadata: Metadata, Optional | contains additional information about the document such as source, date, etc. + embedding: Vector, Optional | the vector representation of the content. + """ + + id: ItemID + content: str + metadata: Optional[Metadata] + embedding: Optional[Vector] + + +"""QueryResults is the response from the vector database for a query/queries. +A query is a list containing one string while queries is a list containing multiple strings. +The response is a list of query results, each query result is a list of tuples containing the document and the distance. +""" +QueryResults = List[List[Tuple[Document, float]]] + + +@runtime_checkable +class VectorDB(Protocol): + """ + Abstract class for vector database. A vector database is responsible for storing and retrieving documents. + + Attributes: + active_collection: Any | The active collection in the vector database. Make get_collection faster. Default is None. + type: str | The type of the vector database, chroma, pgvector, etc. Default is "". + + Methods: + create_collection: Callable[[str, bool, bool], Any] | Create a collection in the vector database. + get_collection: Callable[[str], Any] | Get the collection from the vector database. + delete_collection: Callable[[str], Any] | Delete the collection from the vector database. + insert_docs: Callable[[List[Document], str, bool], None] | Insert documents into the collection of the vector database. + update_docs: Callable[[List[Document], str], None] | Update documents in the collection of the vector database. + delete_docs: Callable[[List[ItemID], str], None] | Delete documents from the collection of the vector database. + retrieve_docs: Callable[[List[str], str, int, float], QueryResults] | Retrieve documents from the collection of the vector database based on the queries. + get_docs_by_ids: Callable[[List[ItemID], str], List[Document]] | Retrieve documents from the collection of the vector database based on the ids. + """ + + active_collection: Any = None + type: str = "" + + def create_collection(self, collection_name: str, overwrite: bool = False, get_or_create: bool = True) -> Any: + """ + Create a collection in the vector database. + Case 1. if the collection does not exist, create the collection. + Case 2. the collection exists, if overwrite is True, it will overwrite the collection. + Case 3. the collection exists and overwrite is False, if get_or_create is True, it will get the collection, + otherwise it raise a ValueError. + + Args: + collection_name: str | The name of the collection. + overwrite: bool | Whether to overwrite the collection if it exists. Default is False. + get_or_create: bool | Whether to get the collection if it exists. Default is True. + + Returns: + Any | The collection object. + """ + ... + + def get_collection(self, collection_name: str = None) -> Any: + """ + Get the collection from the vector database. + + Args: + collection_name: str | The name of the collection. Default is None. If None, return the + current active collection. + + Returns: + Any | The collection object. + """ + ... + + def delete_collection(self, collection_name: str) -> Any: + """ + Delete the collection from the vector database. + + Args: + collection_name: str | The name of the collection. + + Returns: + Any + """ + ... + + def insert_docs(self, docs: List[Document], collection_name: str = None, upsert: bool = False, **kwargs) -> None: + """ + Insert documents into the collection of the vector database. + + Args: + docs: List[Document] | A list of documents. Each document is a TypedDict `Document`. + collection_name: str | The name of the collection. Default is None. + upsert: bool | Whether to update the document if it exists. Default is False. + kwargs: Dict | Additional keyword arguments. + + Returns: + None + """ + ... + + def update_docs(self, docs: List[Document], collection_name: str = None, **kwargs) -> None: + """ + Update documents in the collection of the vector database. + + Args: + docs: List[Document] | A list of documents. + collection_name: str | The name of the collection. Default is None. + kwargs: Dict | Additional keyword arguments. + + Returns: + None + """ + ... + + def delete_docs(self, ids: List[ItemID], collection_name: str = None, **kwargs) -> None: + """ + Delete documents from the collection of the vector database. + + Args: + ids: List[ItemID] | A list of document ids. Each id is a typed `ItemID`. + collection_name: str | The name of the collection. Default is None. + kwargs: Dict | Additional keyword arguments. + + Returns: + None + """ + ... + + def retrieve_docs( + self, + queries: List[str], + collection_name: str = None, + n_results: int = 10, + distance_threshold: float = -1, + **kwargs, + ) -> QueryResults: + """ + Retrieve documents from the collection of the vector database based on the queries. + + Args: + queries: List[str] | A list of queries. Each query is a string. + collection_name: str | The name of the collection. Default is None. + n_results: int | The number of relevant documents to return. Default is 10. + distance_threshold: float | The threshold for the distance score, only distance smaller than it will be + returned. Don't filter with it if < 0. Default is -1. + kwargs: Dict | Additional keyword arguments. + + Returns: + QueryResults | The query results. Each query result is a list of list of tuples containing the document and + the distance. + """ + ... + + def get_docs_by_ids( + self, ids: List[ItemID] = None, collection_name: str = None, include=None, **kwargs + ) -> List[Document]: + """ + Retrieve documents from the collection of the vector database based on the ids. + + Args: + ids: List[ItemID] | A list of document ids. If None, will return all the documents. Default is None. + collection_name: str | The name of the collection. Default is None. + include: List[str] | The fields to include. Default is None. + If None, will include ["metadatas", "documents"], ids will always be included. + kwargs: dict | Additional keyword arguments. + + Returns: + List[Document] | The results. + """ + ... + + +class VectorDBFactory: + """ + Factory class for creating vector databases. + """ + + PREDEFINED_VECTOR_DB = ["chroma", "pgvector"] + + @staticmethod + def create_vector_db(db_type: str, **kwargs) -> VectorDB: + """ + Create a vector database. + + Args: + db_type: str | The type of the vector database. + kwargs: Dict | The keyword arguments for initializing the vector database. + + Returns: + VectorDB | The vector database. + """ + if db_type.lower() in ["chroma", "chromadb"]: + from .chromadb import ChromaVectorDB + + return ChromaVectorDB(**kwargs) + if db_type.lower() in ["pgvector", "pgvectordb"]: + from .pgvectordb import PGVectorDB + + return PGVectorDB(**kwargs) + else: + raise ValueError( + f"Unsupported vector database type: {db_type}. Valid types are {VectorDBFactory.PREDEFINED_VECTOR_DB}." + ) diff --git a/autogen/agentchat/contrib/vectordb/chromadb.py b/autogen/agentchat/contrib/vectordb/chromadb.py new file mode 100644 index 00000000000..3f1fbc86a44 --- /dev/null +++ b/autogen/agentchat/contrib/vectordb/chromadb.py @@ -0,0 +1,318 @@ +import os +from typing import Callable, List + +from .base import Document, ItemID, QueryResults, VectorDB +from .utils import chroma_results_to_query_results, filter_results_by_distance, get_logger + +try: + import chromadb + + if chromadb.__version__ < "0.4.15": + raise ImportError("Please upgrade chromadb to version 0.4.15 or later.") + import chromadb.utils.embedding_functions as ef + from chromadb.api.models.Collection import Collection +except ImportError: + raise ImportError("Please install chromadb: `pip install chromadb`") + +CHROMADB_MAX_BATCH_SIZE = os.environ.get("CHROMADB_MAX_BATCH_SIZE", 40000) +logger = get_logger(__name__) + + +class ChromaVectorDB(VectorDB): + """ + A vector database that uses ChromaDB as the backend. + """ + + def __init__( + self, *, client=None, path: str = "tmp/db", embedding_function: Callable = None, metadata: dict = None, **kwargs + ) -> None: + """ + Initialize the vector database. + + Args: + client: chromadb.Client | The client object of the vector database. Default is None. + If provided, it will use the client object directly and ignore other arguments. + path: str | The path to the vector database. Default is `tmp/db`. The default was `None` for version <=0.2.24. + embedding_function: Callable | The embedding function used to generate the vector representation + of the documents. Default is None, SentenceTransformerEmbeddingFunction("all-MiniLM-L6-v2") will be used. + metadata: dict | The metadata of the vector database. Default is None. If None, it will use this + setting: {"hnsw:space": "ip", "hnsw:construction_ef": 30, "hnsw:M": 32}. For more details of + the metadata, please refer to [distances](https://github.com/nmslib/hnswlib#supported-distances), + [hnsw](https://github.com/chroma-core/chroma/blob/566bc80f6c8ee29f7d99b6322654f32183c368c4/chromadb/segment/impl/vector/local_hnsw.py#L184), + and [ALGO_PARAMS](https://github.com/nmslib/hnswlib/blob/master/ALGO_PARAMS.md). + kwargs: dict | Additional keyword arguments. + + Returns: + None + """ + self.client = client + self.path = path + self.embedding_function = ( + ef.SentenceTransformerEmbeddingFunction("all-MiniLM-L6-v2") + if embedding_function is None + else embedding_function + ) + self.metadata = metadata if metadata else {"hnsw:space": "ip", "hnsw:construction_ef": 30, "hnsw:M": 32} + if not self.client: + if self.path is not None: + self.client = chromadb.PersistentClient(path=self.path, **kwargs) + else: + self.client = chromadb.Client(**kwargs) + self.active_collection = None + self.type = "chroma" + + def create_collection( + self, collection_name: str, overwrite: bool = False, get_or_create: bool = True + ) -> Collection: + """ + Create a collection in the vector database. + Case 1. if the collection does not exist, create the collection. + Case 2. the collection exists, if overwrite is True, it will overwrite the collection. + Case 3. the collection exists and overwrite is False, if get_or_create is True, it will get the collection, + otherwise it raise a ValueError. + + Args: + collection_name: str | The name of the collection. + overwrite: bool | Whether to overwrite the collection if it exists. Default is False. + get_or_create: bool | Whether to get the collection if it exists. Default is True. + + Returns: + Collection | The collection object. + """ + try: + if self.active_collection and self.active_collection.name == collection_name: + collection = self.active_collection + else: + collection = self.client.get_collection(collection_name) + except ValueError: + collection = None + if collection is None: + return self.client.create_collection( + collection_name, + embedding_function=self.embedding_function, + get_or_create=get_or_create, + metadata=self.metadata, + ) + elif overwrite: + self.client.delete_collection(collection_name) + return self.client.create_collection( + collection_name, + embedding_function=self.embedding_function, + get_or_create=get_or_create, + metadata=self.metadata, + ) + elif get_or_create: + return collection + else: + raise ValueError(f"Collection {collection_name} already exists.") + + def get_collection(self, collection_name: str = None) -> Collection: + """ + Get the collection from the vector database. + + Args: + collection_name: str | The name of the collection. Default is None. If None, return the + current active collection. + + Returns: + Collection | The collection object. + """ + if collection_name is None: + if self.active_collection is None: + raise ValueError("No collection is specified.") + else: + logger.info( + f"No collection is specified. Using current active collection {self.active_collection.name}." + ) + else: + if not (self.active_collection and self.active_collection.name == collection_name): + self.active_collection = self.client.get_collection(collection_name) + return self.active_collection + + def delete_collection(self, collection_name: str) -> None: + """ + Delete the collection from the vector database. + + Args: + collection_name: str | The name of the collection. + + Returns: + None + """ + self.client.delete_collection(collection_name) + if self.active_collection and self.active_collection.name == collection_name: + self.active_collection = None + + def _batch_insert( + self, collection: Collection, embeddings=None, ids=None, metadatas=None, documents=None, upsert=False + ) -> None: + batch_size = int(CHROMADB_MAX_BATCH_SIZE) + for i in range(0, len(documents), min(batch_size, len(documents))): + end_idx = i + min(batch_size, len(documents) - i) + collection_kwargs = { + "documents": documents[i:end_idx], + "ids": ids[i:end_idx], + "metadatas": metadatas[i:end_idx] if metadatas else None, + "embeddings": embeddings[i:end_idx] if embeddings else None, + } + if upsert: + collection.upsert(**collection_kwargs) + else: + collection.add(**collection_kwargs) + + def insert_docs(self, docs: List[Document], collection_name: str = None, upsert: bool = False) -> None: + """ + Insert documents into the collection of the vector database. + + Args: + docs: List[Document] | A list of documents. Each document is a TypedDict `Document`. + collection_name: str | The name of the collection. Default is None. + upsert: bool | Whether to update the document if it exists. Default is False. + kwargs: Dict | Additional keyword arguments. + + Returns: + None + """ + if not docs: + return + if docs[0].get("content") is None: + raise ValueError("The document content is required.") + if docs[0].get("id") is None: + raise ValueError("The document id is required.") + documents = [doc.get("content") for doc in docs] + ids = [doc.get("id") for doc in docs] + collection = self.get_collection(collection_name) + if docs[0].get("embedding") is None: + logger.info( + "No content embedding is provided. Will use the VectorDB's embedding function to generate the content embedding." + ) + embeddings = None + else: + embeddings = [doc.get("embedding") for doc in docs] + if docs[0].get("metadata") is None: + metadatas = None + else: + metadatas = [doc.get("metadata") for doc in docs] + self._batch_insert(collection, embeddings, ids, metadatas, documents, upsert) + + def update_docs(self, docs: List[Document], collection_name: str = None) -> None: + """ + Update documents in the collection of the vector database. + + Args: + docs: List[Document] | A list of documents. + collection_name: str | The name of the collection. Default is None. + + Returns: + None + """ + self.insert_docs(docs, collection_name, upsert=True) + + def delete_docs(self, ids: List[ItemID], collection_name: str = None, **kwargs) -> None: + """ + Delete documents from the collection of the vector database. + + Args: + ids: List[ItemID] | A list of document ids. Each id is a typed `ItemID`. + collection_name: str | The name of the collection. Default is None. + kwargs: Dict | Additional keyword arguments. + + Returns: + None + """ + collection = self.get_collection(collection_name) + collection.delete(ids, **kwargs) + + def retrieve_docs( + self, + queries: List[str], + collection_name: str = None, + n_results: int = 10, + distance_threshold: float = -1, + **kwargs, + ) -> QueryResults: + """ + Retrieve documents from the collection of the vector database based on the queries. + + Args: + queries: List[str] | A list of queries. Each query is a string. + collection_name: str | The name of the collection. Default is None. + n_results: int | The number of relevant documents to return. Default is 10. + distance_threshold: float | The threshold for the distance score, only distance smaller than it will be + returned. Don't filter with it if < 0. Default is -1. + kwargs: Dict | Additional keyword arguments. + + Returns: + QueryResults | The query results. Each query result is a list of list of tuples containing the document and + the distance. + """ + collection = self.get_collection(collection_name) + if isinstance(queries, str): + queries = [queries] + results = collection.query( + query_texts=queries, + n_results=n_results, + **kwargs, + ) + results["contents"] = results.pop("documents") + results = chroma_results_to_query_results(results) + results = filter_results_by_distance(results, distance_threshold) + return results + + @staticmethod + def _chroma_get_results_to_list_documents(data_dict) -> List[Document]: + """Converts a dictionary with list values to a list of Document. + + Args: + data_dict: A dictionary where keys map to lists or None. + + Returns: + List[Document] | The list of Document. + + Example: + data_dict = { + "key1s": [1, 2, 3], + "key2s": ["a", "b", "c"], + "key3s": None, + "key4s": ["x", "y", "z"], + } + + results = [ + {"key1": 1, "key2": "a", "key4": "x"}, + {"key1": 2, "key2": "b", "key4": "y"}, + {"key1": 3, "key2": "c", "key4": "z"}, + ] + """ + + results = [] + keys = [key for key in data_dict if data_dict[key] is not None] + + for i in range(len(data_dict[keys[0]])): + sub_dict = {} + for key in data_dict.keys(): + if data_dict[key] is not None and len(data_dict[key]) > i: + sub_dict[key[:-1]] = data_dict[key][i] + results.append(sub_dict) + return results + + def get_docs_by_ids( + self, ids: List[ItemID] = None, collection_name: str = None, include=None, **kwargs + ) -> List[Document]: + """ + Retrieve documents from the collection of the vector database based on the ids. + + Args: + ids: List[ItemID] | A list of document ids. If None, will return all the documents. Default is None. + collection_name: str | The name of the collection. Default is None. + include: List[str] | The fields to include. Default is None. + If None, will include ["metadatas", "documents"], ids will always be included. + kwargs: dict | Additional keyword arguments. + + Returns: + List[Document] | The results. + """ + collection = self.get_collection(collection_name) + include = include if include else ["metadatas", "documents"] + results = collection.get(ids, include=include, **kwargs) + results = self._chroma_get_results_to_list_documents(results) + return results diff --git a/autogen/agentchat/contrib/vectordb/pgvectordb.py b/autogen/agentchat/contrib/vectordb/pgvectordb.py new file mode 100644 index 00000000000..b5db55f7eb1 --- /dev/null +++ b/autogen/agentchat/contrib/vectordb/pgvectordb.py @@ -0,0 +1,888 @@ +import os +import re +import urllib.parse +from typing import Callable, List + +import numpy as np +from sentence_transformers import SentenceTransformer + +from .base import Document, ItemID, QueryResults, VectorDB +from .utils import get_logger + +try: + import pgvector + from pgvector.psycopg import register_vector +except ImportError: + raise ImportError("Please install pgvector: `pip install pgvector`") + +try: + import psycopg +except ImportError: + raise ImportError("Please install pgvector: `pip install psycopg`") + +PGVECTOR_MAX_BATCH_SIZE = os.environ.get("PGVECTOR_MAX_BATCH_SIZE", 40000) +logger = get_logger(__name__) + + +class Collection: + """ + A Collection object for PGVector. + + Attributes: + client: The PGVector client. + collection_name (str): The name of the collection. Default is "documents". + embedding_function (Callable): The embedding function used to generate the vector representation. + metadata (Optional[dict]): The metadata of the collection. + get_or_create (Optional): The flag indicating whether to get or create the collection. + model_name: (Optional str) | Sentence embedding model to use. Models can be chosen from: + https://huggingface.co/models?library=sentence-transformers + """ + + def __init__( + self, + client=None, + collection_name: str = "autogen-docs", + embedding_function: Callable = None, + metadata=None, + get_or_create=None, + model_name="all-MiniLM-L6-v2", + ): + """ + Initialize the Collection object. + + Args: + client: The PostgreSQL client. + collection_name: The name of the collection. Default is "documents". + embedding_function: The embedding function used to generate the vector representation. + metadata: The metadata of the collection. + get_or_create: The flag indicating whether to get or create the collection. + model_name: | Sentence embedding model to use. Models can be chosen from: + https://huggingface.co/models?library=sentence-transformers + Returns: + None + """ + self.client = client + self.embedding_function = embedding_function + self.model_name = model_name + self.name = self.set_collection_name(collection_name) + self.require_embeddings_or_documents = False + self.ids = [] + try: + self.embedding_function = ( + SentenceTransformer(self.model_name) if embedding_function is None else embedding_function + ) + except Exception as e: + logger.error( + f"Validate the model name entered: {self.model_name} " + f"from https://huggingface.co/models?library=sentence-transformers\nError: {e}" + ) + raise e + self.metadata = metadata if metadata else {"hnsw:space": "ip", "hnsw:construction_ef": 32, "hnsw:M": 16} + self.documents = "" + self.get_or_create = get_or_create + + def set_collection_name(self, collection_name) -> str: + name = re.sub("-", "_", collection_name) + self.name = name + return self.name + + def add(self, ids: List[ItemID], documents: List, embeddings: List = None, metadatas: List = None) -> None: + """ + Add documents to the collection. + + Args: + ids (List[ItemID]): A list of document IDs. + embeddings (List): A list of document embeddings. Optional + metadatas (List): A list of document metadatas. Optional + documents (List): A list of documents. + + Returns: + None + """ + cursor = self.client.cursor() + sql_values = [] + if embeddings is not None and metadatas is not None: + for doc_id, embedding, metadata, document in zip(ids, embeddings, metadatas, documents): + metadata = re.sub("'", '"', str(metadata)) + sql_values.append((doc_id, embedding, metadata, document)) + sql_string = ( + f"INSERT INTO {self.name} (id, embedding, metadatas, documents)\n" f"VALUES (%s, %s, %s, %s);\n" + ) + elif embeddings is not None: + for doc_id, embedding, document in zip(ids, embeddings, documents): + sql_values.append((doc_id, embedding, document)) + sql_string = f"INSERT INTO {self.name} (id, embedding, documents) " f"VALUES (%s, %s, %s);\n" + elif metadatas is not None: + for doc_id, metadata, document in zip(ids, metadatas, documents): + metadata = re.sub("'", '"', str(metadata)) + embedding = self.embedding_function.encode(document) + sql_values.append((doc_id, metadata, embedding, document)) + sql_string = ( + f"INSERT INTO {self.name} (id, metadatas, embedding, documents)\n" f"VALUES (%s, %s, %s, %s);\n" + ) + else: + for doc_id, document in zip(ids, documents): + embedding = self.embedding_function.encode(document) + sql_values.append((doc_id, document, embedding)) + sql_string = f"INSERT INTO {self.name} (id, documents, embedding)\n" f"VALUES (%s, %s, %s);\n" + logger.debug(f"Add SQL String:\n{sql_string}\n{sql_values}") + cursor.executemany(sql_string, sql_values) + cursor.close() + + def upsert(self, ids: List[ItemID], documents: List, embeddings: List = None, metadatas: List = None) -> None: + """ + Upsert documents into the collection. + + Args: + ids (List[ItemID]): A list of document IDs. + documents (List): A list of documents. + embeddings (List): A list of document embeddings. + metadatas (List): A list of document metadatas. + + Returns: + None + """ + cursor = self.client.cursor() + sql_values = [] + if embeddings is not None and metadatas is not None: + for doc_id, embedding, metadata, document in zip(ids, embeddings, metadatas, documents): + metadata = re.sub("'", '"', str(metadata)) + sql_values.append((doc_id, embedding, metadata, document, embedding, metadata, document)) + sql_string = ( + f"INSERT INTO {self.name} (id, embedding, metadatas, documents)\n" + f"VALUES (%s, %s, %s, %s)\n" + f"ON CONFLICT (id)\n" + f"DO UPDATE SET embedding = %s,\n" + f"metadatas = %s, documents = %s;\n" + ) + elif embeddings is not None: + for doc_id, embedding, document in zip(ids, embeddings, documents): + sql_values.append((doc_id, embedding, document, embedding, document)) + sql_string = ( + f"INSERT INTO {self.name} (id, embedding, documents) " + f"VALUES (%s, %s, %s) ON CONFLICT (id)\n" + f"DO UPDATE SET embedding = %s, documents = %s;\n" + ) + elif metadatas is not None: + for doc_id, metadata, document in zip(ids, metadatas, documents): + metadata = re.sub("'", '"', str(metadata)) + embedding = self.embedding_function.encode(document) + sql_values.append((doc_id, metadata, embedding, document, metadata, document, embedding)) + sql_string = ( + f"INSERT INTO {self.name} (id, metadatas, embedding, documents)\n" + f"VALUES (%s, %s, %s, %s)\n" + f"ON CONFLICT (id)\n" + f"DO UPDATE SET metadatas = %s, documents = %s, embedding = %s;\n" + ) + else: + for doc_id, document in zip(ids, documents): + embedding = self.embedding_function.encode(document) + sql_values.append((doc_id, document, embedding, document)) + sql_string = ( + f"INSERT INTO {self.name} (id, documents, embedding)\n" + f"VALUES (%s, %s, %s)\n" + f"ON CONFLICT (id)\n" + f"DO UPDATE SET documents = %s;\n" + ) + logger.debug(f"Upsert SQL String:\n{sql_string}\n{sql_values}") + cursor.executemany(sql_string, sql_values) + cursor.close() + + def count(self) -> int: + """ + Get the total number of documents in the collection. + + Returns: + int: The total number of documents. + """ + cursor = self.client.cursor() + query = f"SELECT COUNT(*) FROM {self.name}" + cursor.execute(query) + total = cursor.fetchone()[0] + cursor.close() + try: + total = int(total) + except (TypeError, ValueError): + total = None + return total + + def table_exists(self, table_name: str) -> bool: + """ + Check if a table exists in the PostgreSQL database. + + Args: + table_name (str): The name of the table to check. + + Returns: + bool: True if the table exists, False otherwise. + """ + + cursor = self.client.cursor() + cursor.execute( + """ + SELECT EXISTS ( + SELECT 1 + FROM information_schema.tables + WHERE table_name = %s + ) + """, + (table_name,), + ) + exists = cursor.fetchone()[0] + return exists + + def get(self, ids=None, include=None, where=None, limit=None, offset=None) -> List[Document]: + """ + Retrieve documents from the collection. + + Args: + ids (Optional[List]): A list of document IDs. + include (Optional): The fields to include. + where (Optional): Additional filtering criteria. + limit (Optional): The maximum number of documents to retrieve. + offset (Optional): The offset for pagination. + + Returns: + List: The retrieved documents. + """ + cursor = self.client.cursor() + + # Initialize variables for query components + select_clause = "SELECT id, metadatas, documents, embedding" + from_clause = f"FROM {self.name}" + where_clause = "" + limit_clause = "" + offset_clause = "" + + # Handle include clause + if include: + select_clause = f"SELECT id, {', '.join(include)}, embedding" + + # Handle where clause + if ids: + where_clause = f"WHERE id IN ({', '.join(['%s' for _ in ids])})" + elif where: + where_clause = f"WHERE {where}" + + # Handle limit and offset clauses + if limit: + limit_clause = "LIMIT %s" + if offset: + offset_clause = "OFFSET %s" + + # Construct the full query + query = f"{select_clause} {from_clause} {where_clause} {limit_clause} {offset_clause}" + + retrieved_documents = [] + try: + # Execute the query with the appropriate values + if ids is not None: + cursor.execute(query, ids) + else: + query_params = [] + if limit: + query_params.append(limit) + if offset: + query_params.append(offset) + cursor.execute(query, query_params) + + retrieval = cursor.fetchall() + for retrieved_document in retrieval: + retrieved_documents.append( + Document( + id=retrieved_document[0].strip(), + metadata=retrieved_document[1], + content=retrieved_document[2], + embedding=retrieved_document[3], + ) + ) + except (psycopg.errors.UndefinedTable, psycopg.errors.UndefinedColumn) as e: + logger.info(f"Error executing select on non-existent table: {self.name}. Creating it instead. Error: {e}") + self.create_collection(collection_name=self.name) + logger.info(f"Created table {self.name}") + + cursor.close() + return retrieved_documents + + def update(self, ids: List, embeddings: List, metadatas: List, documents: List) -> None: + """ + Update documents in the collection. + + Args: + ids (List): A list of document IDs. + embeddings (List): A list of document embeddings. + metadatas (List): A list of document metadatas. + documents (List): A list of documents. + + Returns: + None + """ + cursor = self.client.cursor() + sql_values = [] + for doc_id, embedding, metadata, document in zip(ids, embeddings, metadatas, documents): + sql_values.append((doc_id, embedding, metadata, document, doc_id, embedding, metadata, document)) + sql_string = ( + f"INSERT INTO {self.name} (id, embedding, metadata, document) " + f"VALUES (%s, %s, %s, %s) " + f"ON CONFLICT (id) " + f"DO UPDATE SET id = %s, embedding = %s, " + f"metadata = %s, document = %s;\n" + ) + logger.debug(f"Upsert SQL String:\n{sql_string}\n") + cursor.executemany(sql_string, sql_values) + cursor.close() + + @staticmethod + def euclidean_distance(arr1: List[float], arr2: List[float]) -> float: + """ + Calculate the Euclidean distance between two vectors. + + Parameters: + - arr1 (List[float]): The first vector. + - arr2 (List[float]): The second vector. + + Returns: + - float: The Euclidean distance between arr1 and arr2. + """ + dist = np.linalg.norm(arr1 - arr2) + return dist + + @staticmethod + def cosine_distance(arr1: List[float], arr2: List[float]) -> float: + """ + Calculate the cosine distance between two vectors. + + Parameters: + - arr1 (List[float]): The first vector. + - arr2 (List[float]): The second vector. + + Returns: + - float: The cosine distance between arr1 and arr2. + """ + dist = np.dot(arr1, arr2) / (np.linalg.norm(arr1) * np.linalg.norm(arr2)) + return dist + + @staticmethod + def inner_product_distance(arr1: List[float], arr2: List[float]) -> float: + """ + Calculate the Euclidean distance between two vectors. + + Parameters: + - arr1 (List[float]): The first vector. + - arr2 (List[float]): The second vector. + + Returns: + - float: The Euclidean distance between arr1 and arr2. + """ + dist = np.linalg.norm(arr1 - arr2) + return dist + + def query( + self, + query_texts: List[str], + collection_name: str = None, + n_results: int = 10, + distance_type: str = "euclidean", + distance_threshold: float = -1, + include_embedding: bool = False, + ) -> QueryResults: + """ + Query documents in the collection. + + Args: + query_texts (List[str]): A list of query texts. + collection_name (Optional[str]): The name of the collection. + n_results (int): The maximum number of results to return. + distance_type (Optional[str]): Distance search type - euclidean or cosine + distance_threshold (Optional[float]): Distance threshold to limit searches + include_embedding (Optional[bool]): Include embedding values in QueryResults + Returns: + QueryResults: The query results. + """ + if collection_name: + self.name = collection_name + + clause = "ORDER BY" + if distance_threshold == -1: + distance_threshold = "" + clause = "ORDER BY" + elif distance_threshold > 0: + distance_threshold = f"< {distance_threshold}" + clause = "WHERE" + + cursor = self.client.cursor() + results = [] + for query_text in query_texts: + vector = self.embedding_function.encode(query_text, convert_to_tensor=False).tolist() + if distance_type.lower() == "cosine": + index_function = "<=>" + elif distance_type.lower() == "euclidean": + index_function = "<->" + elif distance_type.lower() == "inner-product": + index_function = "<#>" + else: + index_function = "<->" + query = ( + f"SELECT id, documents, embedding, metadatas " + f"FROM {self.name} " + f"{clause} embedding {index_function} '{str(vector)}' {distance_threshold} " + f"LIMIT {n_results}" + ) + cursor.execute(query) + result = [] + for row in cursor.fetchall(): + fetched_document = Document(id=row[0].strip(), content=row[1], embedding=row[2], metadata=row[3]) + fetched_document_array = self.convert_string_to_array(array_string=fetched_document.get("embedding")) + if distance_type.lower() == "cosine": + distance = self.cosine_distance(fetched_document_array, vector) + elif distance_type.lower() == "euclidean": + distance = self.euclidean_distance(fetched_document_array, vector) + elif distance_type.lower() == "inner-product": + distance = self.inner_product_distance(fetched_document_array, vector) + else: + distance = self.euclidean_distance(fetched_document_array, vector) + if not include_embedding: + fetched_document = Document(id=row[0].strip(), content=row[1], metadata=row[3]) + result.append((fetched_document, distance)) + results.append(result) + cursor.close() + logger.debug(f"Query Results: {results}") + return results + + @staticmethod + def convert_string_to_array(array_string) -> List[float]: + """ + Convert a string representation of an array to a list of floats. + + Parameters: + - array_string (str): The string representation of the array. + + Returns: + - list: A list of floats parsed from the input string. If the input is + not a string, it returns the input itself. + """ + if not isinstance(array_string, str): + return array_string + array_string = array_string.strip("[]") + array = [float(num) for num in array_string.split()] + return array + + def modify(self, metadata, collection_name: str = None) -> None: + """ + Modify metadata for the collection. + + Args: + collection_name: The name of the collection. + metadata: The new metadata. + + Returns: + None + """ + if collection_name: + self.name = collection_name + cursor = self.client.cursor() + cursor.execute( + "UPDATE collections" "SET metadata = '%s'" "WHERE collection_name = '%s';", (metadata, self.name) + ) + cursor.close() + + def delete(self, ids: List[ItemID], collection_name: str = None) -> None: + """ + Delete documents from the collection. + + Args: + ids (List[ItemID]): A list of document IDs to delete. + collection_name (str): The name of the collection to delete. + + Returns: + None + """ + if collection_name: + self.name = collection_name + cursor = self.client.cursor() + id_placeholders = ", ".join(["%s" for _ in ids]) + cursor.execute(f"DELETE FROM {self.name} WHERE id IN ({id_placeholders});", ids) + cursor.close() + + def delete_collection(self, collection_name: str = None) -> None: + """ + Delete the entire collection. + + Args: + collection_name (Optional[str]): The name of the collection to delete. + + Returns: + None + """ + if collection_name: + self.name = collection_name + cursor = self.client.cursor() + cursor.execute(f"DROP TABLE IF EXISTS {self.name}") + cursor.close() + + def create_collection(self, collection_name: str = None) -> None: + """ + Create a new collection. + + Args: + collection_name (Optional[str]): The name of the new collection. + + Returns: + None + """ + if collection_name: + self.name = collection_name + cursor = self.client.cursor() + cursor.execute( + f"CREATE TABLE {self.name} (" + f"documents text, id CHAR(8) PRIMARY KEY, metadatas JSONB, embedding vector(384));" + f"CREATE INDEX " + f'ON {self.name} USING hnsw (embedding vector_l2_ops) WITH (m = {self.metadata["hnsw:M"]}, ' + f'ef_construction = {self.metadata["hnsw:construction_ef"]});' + f"CREATE INDEX " + f'ON {self.name} USING hnsw (embedding vector_cosine_ops) WITH (m = {self.metadata["hnsw:M"]}, ' + f'ef_construction = {self.metadata["hnsw:construction_ef"]});' + f"CREATE INDEX " + f'ON {self.name} USING hnsw (embedding vector_ip_ops) WITH (m = {self.metadata["hnsw:M"]}, ' + f'ef_construction = {self.metadata["hnsw:construction_ef"]});' + ) + cursor.close() + + +class PGVectorDB(VectorDB): + """ + A vector database that uses PGVector as the backend. + """ + + def __init__( + self, + *, + connection_string: str = None, + host: str = None, + port: int = None, + dbname: str = None, + username: str = None, + password: str = None, + connect_timeout: int = 10, + embedding_function: Callable = None, + metadata: dict = None, + model_name: str = "all-MiniLM-L6-v2", + ) -> None: + """ + Initialize the vector database. + + Note: connection_string or host + port + dbname must be specified + + Args: + connection_string: "postgresql://username:password@hostname:port/database" | The PGVector connection string. Default is None. + host: str | The host to connect to. Default is None. + port: int | The port to connect to. Default is None. + dbname: str | The database name to connect to. Default is None. + username: str | The database username to use. Default is None. + password: str | The database user password to use. Default is None. + connect_timeout: int | The timeout to set for the connection. Default is 10. + embedding_function: Callable | The embedding function used to generate the vector representation + of the documents. Default is None. + metadata: dict | The metadata of the vector database. Default is None. If None, it will use this + setting: {"hnsw:space": "ip", "hnsw:construction_ef": 30, "hnsw:M": 16}. Creates Index on table + using hnsw (embedding vector_l2_ops) WITH (m = hnsw:M) ef_construction = "hnsw:construction_ef". + For more info: https://github.com/pgvector/pgvector?tab=readme-ov-file#hnsw + model_name: str | Sentence embedding model to use. Models can be chosen from: + https://huggingface.co/models?library=sentence-transformers + + Returns: + None + """ + try: + if connection_string: + parsed_connection = urllib.parse.urlparse(connection_string) + encoded_username = urllib.parse.quote(parsed_connection.username, safe="") + encoded_password = urllib.parse.quote(parsed_connection.password, safe="") + encoded_host = urllib.parse.quote(parsed_connection.hostname, safe="") + encoded_database = urllib.parse.quote(parsed_connection.path[1:], safe="") + connection_string_encoded = ( + f"{parsed_connection.scheme}://{encoded_username}:{encoded_password}" + f"@{encoded_host}:{parsed_connection.port}/{encoded_database}" + ) + self.client = psycopg.connect(conninfo=connection_string_encoded, autocommit=True) + elif host and port and dbname: + self.client = psycopg.connect( + host=host, + port=port, + dbname=dbname, + username=username, + password=password, + connect_timeout=connect_timeout, + autocommit=True, + ) + except psycopg.Error as e: + logger.error("Error connecting to the database: ", e) + raise e + self.model_name = model_name + try: + self.embedding_function = ( + SentenceTransformer(self.model_name) if embedding_function is None else embedding_function + ) + except Exception as e: + logger.error( + f"Validate the model name entered: {self.model_name} " + f"from https://huggingface.co/models?library=sentence-transformers\nError: {e}" + ) + raise e + self.metadata = metadata + self.client.execute("CREATE EXTENSION IF NOT EXISTS vector") + register_vector(self.client) + self.active_collection = None + + def create_collection( + self, collection_name: str, overwrite: bool = False, get_or_create: bool = True + ) -> Collection: + """ + Create a collection in the vector database. + Case 1. if the collection does not exist, create the collection. + Case 2. the collection exists, if overwrite is True, it will overwrite the collection. + Case 3. the collection exists and overwrite is False, if get_or_create is True, it will get the collection, + otherwise it raise a ValueError. + + Args: + collection_name: str | The name of the collection. + overwrite: bool | Whether to overwrite the collection if it exists. Default is False. + get_or_create: bool | Whether to get the collection if it exists. Default is True. + + Returns: + Collection | The collection object. + """ + try: + if self.active_collection and self.active_collection.name == collection_name: + collection = self.active_collection + else: + collection = self.get_collection(collection_name) + except ValueError: + collection = None + if collection is None: + collection = Collection( + client=self.client, + collection_name=collection_name, + embedding_function=self.embedding_function, + get_or_create=get_or_create, + metadata=self.metadata, + model_name=self.model_name, + ) + collection.set_collection_name(collection_name=collection_name) + collection.create_collection(collection_name=collection_name) + return collection + elif overwrite: + self.delete_collection(collection_name) + collection = Collection( + client=self.client, + collection_name=collection_name, + embedding_function=self.embedding_function, + get_or_create=get_or_create, + metadata=self.metadata, + model_name=self.model_name, + ) + collection.set_collection_name(collection_name=collection_name) + collection.create_collection(collection_name=collection_name) + return collection + elif get_or_create: + return collection + elif not collection.table_exists(table_name=collection_name): + collection = Collection( + client=self.client, + collection_name=collection_name, + embedding_function=self.embedding_function, + get_or_create=get_or_create, + metadata=self.metadata, + model_name=self.model_name, + ) + collection.set_collection_name(collection_name=collection_name) + collection.create_collection(collection_name=collection_name) + return collection + else: + raise ValueError(f"Collection {collection_name} already exists.") + + def get_collection(self, collection_name: str = None) -> Collection: + """ + Get the collection from the vector database. + + Args: + collection_name: str | The name of the collection. Default is None. If None, return the + current active collection. + + Returns: + Collection | The collection object. + """ + if collection_name is None: + if self.active_collection is None: + raise ValueError("No collection is specified.") + else: + logger.debug( + f"No collection is specified. Using current active collection {self.active_collection.name}." + ) + else: + if not (self.active_collection and self.active_collection.name == collection_name): + self.active_collection = Collection( + client=self.client, + collection_name=collection_name, + embedding_function=self.embedding_function, + model_name=self.model_name, + ) + return self.active_collection + + def delete_collection(self, collection_name: str) -> None: + """ + Delete the collection from the vector database. + + Args: + collection_name: str | The name of the collection. + + Returns: + None + """ + if self.active_collection: + self.active_collection.delete_collection(collection_name) + else: + collection = self.get_collection(collection_name) + collection.delete_collection(collection_name) + if self.active_collection and self.active_collection.name == collection_name: + self.active_collection = None + + def _batch_insert( + self, collection: Collection, embeddings=None, ids=None, metadatas=None, documents=None, upsert=False + ) -> None: + batch_size = int(PGVECTOR_MAX_BATCH_SIZE) + default_metadata = {"hnsw:space": "ip", "hnsw:construction_ef": 32, "hnsw:M": 16} + default_metadatas = [default_metadata] * min(batch_size, len(documents)) + for i in range(0, len(documents), min(batch_size, len(documents))): + end_idx = i + min(batch_size, len(documents) - i) + collection_kwargs = { + "documents": documents[i:end_idx], + "ids": ids[i:end_idx], + "metadatas": metadatas[i:end_idx] if metadatas else default_metadatas, + "embeddings": embeddings[i:end_idx] if embeddings else None, + } + if upsert: + collection.upsert(**collection_kwargs) + else: + collection.add(**collection_kwargs) + + def insert_docs(self, docs: List[Document], collection_name: str = None, upsert: bool = False) -> None: + """ + Insert documents into the collection of the vector database. + + Args: + docs: List[Document] | A list of documents. Each document is a TypedDict `Document`. + collection_name: str | The name of the collection. Default is None. + upsert: bool | Whether to update the document if it exists. Default is False. + kwargs: Dict | Additional keyword arguments. + + Returns: + None + """ + if not docs: + return + if docs[0].get("content") is None: + raise ValueError("The document content is required.") + if docs[0].get("id") is None: + raise ValueError("The document id is required.") + documents = [doc.get("content") for doc in docs] + ids = [doc.get("id") for doc in docs] + + collection = self.get_collection(collection_name) + if docs[0].get("embedding") is None: + logger.debug( + "No content embedding is provided. " + "Will use the VectorDB's embedding function to generate the content embedding." + ) + embeddings = None + else: + embeddings = [doc.get("embedding") for doc in docs] + if docs[0].get("metadata") is None: + metadatas = None + else: + metadatas = [doc.get("metadata") for doc in docs] + + self._batch_insert(collection, embeddings, ids, metadatas, documents, upsert) + + def update_docs(self, docs: List[Document], collection_name: str = None) -> None: + """ + Update documents in the collection of the vector database. + + Args: + docs: List[Document] | A list of documents. + collection_name: str | The name of the collection. Default is None. + + Returns: + None + """ + self.insert_docs(docs, collection_name, upsert=True) + + def delete_docs(self, ids: List[ItemID], collection_name: str = None) -> None: + """ + Delete documents from the collection of the vector database. + + Args: + ids: List[ItemID] | A list of document ids. Each id is a typed `ItemID`. + collection_name: str | The name of the collection. Default is None. + kwargs: Dict | Additional keyword arguments. + + Returns: + None + """ + collection = self.get_collection(collection_name) + collection.delete(ids=ids, collection_name=collection_name) + + def retrieve_docs( + self, + queries: List[str], + collection_name: str = None, + n_results: int = 10, + distance_threshold: float = -1, + ) -> QueryResults: + """ + Retrieve documents from the collection of the vector database based on the queries. + + Args: + queries: List[str] | A list of queries. Each query is a string. + collection_name: str | The name of the collection. Default is None. + n_results: int | The number of relevant documents to return. Default is 10. + distance_threshold: float | The threshold for the distance score, only distance smaller than it will be + returned. Don't filter with it if < 0. Default is -1. + kwargs: Dict | Additional keyword arguments. + + Returns: + QueryResults | The query results. Each query result is a list of list of tuples containing the document and + the distance. + """ + collection = self.get_collection(collection_name) + if isinstance(queries, str): + queries = [queries] + results = collection.query( + query_texts=queries, + n_results=n_results, + distance_threshold=distance_threshold, + ) + logger.debug(f"Retrieve Docs Results:\n{results}") + return results + + def get_docs_by_ids( + self, ids: List[ItemID] = None, collection_name: str = None, include=None, **kwargs + ) -> List[Document]: + """ + Retrieve documents from the collection of the vector database based on the ids. + + Args: + ids: List[ItemID] | A list of document ids. If None, will return all the documents. Default is None. + collection_name: str | The name of the collection. Default is None. + include: List[str] | The fields to include. Default is None. + If None, will include ["metadatas", "documents"], ids will always be included. + kwargs: dict | Additional keyword arguments. + + Returns: + List[Document] | The results. + """ + collection = self.get_collection(collection_name) + include = include if include else ["metadatas", "documents"] + results = collection.get(ids, include=include, **kwargs) + logger.debug(f"Retrieve Documents by ID Results:\n{results}") + return results diff --git a/autogen/agentchat/contrib/vectordb/utils.py b/autogen/agentchat/contrib/vectordb/utils.py new file mode 100644 index 00000000000..3dcf79f1f55 --- /dev/null +++ b/autogen/agentchat/contrib/vectordb/utils.py @@ -0,0 +1,115 @@ +import logging +from typing import Any, Dict, List + +from termcolor import colored + +from .base import QueryResults + + +class ColoredLogger(logging.Logger): + def __init__(self, name, level=logging.NOTSET): + super().__init__(name, level) + + def debug(self, msg, *args, color=None, **kwargs): + super().debug(colored(msg, color), *args, **kwargs) + + def info(self, msg, *args, color=None, **kwargs): + super().info(colored(msg, color), *args, **kwargs) + + def warning(self, msg, *args, color="yellow", **kwargs): + super().warning(colored(msg, color), *args, **kwargs) + + def error(self, msg, *args, color="light_red", **kwargs): + super().error(colored(msg, color), *args, **kwargs) + + def critical(self, msg, *args, color="red", **kwargs): + super().critical(colored(msg, color), *args, **kwargs) + + def fatal(self, msg, *args, color="red", **kwargs): + super().fatal(colored(msg, color), *args, **kwargs) + + +def get_logger(name: str, level: int = logging.INFO) -> ColoredLogger: + logger = ColoredLogger(name, level) + console_handler = logging.StreamHandler() + logger.addHandler(console_handler) + formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") + logger.handlers[0].setFormatter(formatter) + return logger + + +logger = get_logger(__name__) + + +def filter_results_by_distance(results: QueryResults, distance_threshold: float = -1) -> QueryResults: + """Filters results based on a distance threshold. + + Args: + results: QueryResults | The query results. List[List[Tuple[Document, float]]] + distance_threshold: The maximum distance allowed for results. + + Returns: + QueryResults | A filtered results containing only distances smaller than the threshold. + """ + + if distance_threshold > 0: + results = [[(key, value) for key, value in data if value < distance_threshold] for data in results] + + return results + + +def chroma_results_to_query_results(data_dict: Dict[str, List[List[Any]]], special_key="distances") -> QueryResults: + """Converts a dictionary with list-of-list values to a list of tuples. + + Args: + data_dict: A dictionary where keys map to lists of lists or None. + special_key: The key in the dictionary containing the special values + for each tuple. + + Returns: + A list of tuples, where each tuple contains a sub-dictionary with + some keys from the original dictionary and the value from the + special_key. + + Example: + data_dict = { + "key1s": [[1, 2, 3], [4, 5, 6], [7, 8, 9]], + "key2s": [["a", "b", "c"], ["c", "d", "e"], ["e", "f", "g"]], + "key3s": None, + "key4s": [["x", "y", "z"], ["1", "2", "3"], ["4", "5", "6"]], + "distances": [[0.1, 0.2, 0.3], [0.4, 0.5, 0.6], [0.7, 0.8, 0.9]], + } + + results = [ + [ + ({"key1": 1, "key2": "a", "key4": "x"}, 0.1), + ({"key1": 2, "key2": "b", "key4": "y"}, 0.2), + ({"key1": 3, "key2": "c", "key4": "z"}, 0.3), + ], + [ + ({"key1": 4, "key2": "c", "key4": "1"}, 0.4), + ({"key1": 5, "key2": "d", "key4": "2"}, 0.5), + ({"key1": 6, "key2": "e", "key4": "3"}, 0.6), + ], + [ + ({"key1": 7, "key2": "e", "key4": "4"}, 0.7), + ({"key1": 8, "key2": "f", "key4": "5"}, 0.8), + ({"key1": 9, "key2": "g", "key4": "6"}, 0.9), + ], + ] + """ + + keys = [key for key in data_dict if key != special_key] + result = [] + + for i in range(len(data_dict[special_key])): + sub_result = [] + for j, distance in enumerate(data_dict[special_key][i]): + sub_dict = {} + for key in keys: + if data_dict[key] is not None and len(data_dict[key]) > i: + sub_dict[key[:-1]] = data_dict[key][i][j] # remove 's' in the end from key + sub_result.append((sub_dict, distance)) + result.append(sub_result) + + return result diff --git a/autogen/agentchat/contrib/web_surfer.py b/autogen/agentchat/contrib/web_surfer.py index 6cd71dc636d..1a54aeebe15 100644 --- a/autogen/agentchat/contrib/web_surfer.py +++ b/autogen/agentchat/contrib/web_surfer.py @@ -1,16 +1,18 @@ -import json import copy +import json import logging import re from dataclasses import dataclass -from typing import Any, Dict, List, Optional, Union, Callable, Literal, Tuple +from datetime import datetime +from typing import Any, Callable, Dict, List, Literal, Optional, Tuple, Union + from typing_extensions import Annotated -from ... import Agent, ConversableAgent, AssistantAgent, UserProxyAgent, GroupChatManager, GroupChat, OpenAIWrapper + +from ... import Agent, AssistantAgent, ConversableAgent, GroupChat, GroupChatManager, OpenAIWrapper, UserProxyAgent from ...browser_utils import SimpleTextBrowser from ...code_utils import content_str -from datetime import datetime -from ...token_count_utils import count_token, get_max_token_limit from ...oai.openai_utils import filter_config +from ...token_count_utils import count_token, get_max_token_limit logger = logging.getLogger(__name__) diff --git a/autogen/agentchat/conversable_agent.py b/autogen/agentchat/conversable_agent.py index 9a99386b84f..c3394a96bb6 100644 --- a/autogen/agentchat/conversable_agent.py +++ b/autogen/agentchat/conversable_agent.py @@ -7,7 +7,6 @@ import re import warnings from collections import defaultdict -from functools import partial from typing import Any, Callable, Dict, List, Literal, Optional, Tuple, Type, TypeVar, Union from openai import BadRequestError @@ -17,6 +16,7 @@ from .._pydantic import model_dump from ..cache.cache import AbstractCache from ..code_utils import ( + PYTHON_VARIANTS, UNKNOWN, check_can_use_docker_or_throw, content_str, @@ -29,10 +29,10 @@ from ..coding.factory import CodeExecutorFactory from ..formatting_utils import colored from ..function_utils import get_function_schema, load_basemodels_if_needed, serialize_to_str +from ..io.base import IOStream from ..oai.client import ModelClient, OpenAIWrapper -from ..runtime_logging import log_new_agent, logging_enabled +from ..runtime_logging import log_event, log_new_agent, logging_enabled from .agent import Agent, LLMAgent -from ..io.base import IOStream from .chat import ChatResult, a_initiate_chats, initiate_chats from .utils import consolidate_chat_info, gather_usage_summary @@ -76,6 +76,7 @@ def __init__( llm_config: Optional[Union[Dict, Literal[False]]] = None, default_auto_reply: Union[str, Dict] = "", description: Optional[str] = None, + chat_messages: Optional[Dict[Agent, List[Dict]]] = None, ): """ Args: @@ -121,6 +122,9 @@ def __init__( default_auto_reply (str or dict): default auto reply when no code execution or llm-based reply is generated. description (str): a short description of the agent. This description is used by other agents (e.g. the GroupChatManager) to decide when to call upon this agent. (Default: system_message) + chat_messages (dict or None): the previous chat messages that this agent had in the past with other agents. + Can be used to give the agent a memory by providing the chat history. This will allow the agent to + resume previous had conversations. Defaults to an empty chat history. """ # we change code_execution_config below and we have to make sure we don't change the input # in case of UserProxyAgent, without this we could even change the default value {} @@ -130,7 +134,11 @@ def __init__( self._name = name # a dictionary of conversations, default value is list - self._oai_messages = defaultdict(list) + if chat_messages is None: + self._oai_messages = defaultdict(list) + else: + self._oai_messages = chat_messages + self._oai_system_message = [{"content": system_message, "role": "system"}] self._description = description if description is not None else system_message self._is_termination_msg = ( @@ -138,6 +146,15 @@ def __init__( if is_termination_msg is not None else (lambda x: content_str(x.get("content")) == "TERMINATE") ) + # Take a copy to avoid modifying the given dict + if isinstance(llm_config, dict): + try: + llm_config = copy.deepcopy(llm_config) + except TypeError as e: + raise TypeError( + "Please implement __deepcopy__ method for each value class in llm_config to support deepcopy." + " Refer to the docs for more details: https://microsoft.github.io/autogen/docs/topics/llm_configuration#adding-http-client-in-llm_config-for-proxy" + ) from e self._validate_llm_config(llm_config) @@ -416,10 +433,15 @@ def reply_func_from_nested_chats( reply_func_from_nested_chats = self._summary_from_nested_chats if not callable(reply_func_from_nested_chats): raise ValueError("reply_func_from_nested_chats must be a callable") - reply_func = partial(reply_func_from_nested_chats, chat_queue) + + def wrapped_reply_func(recipient, messages=None, sender=None, config=None): + return reply_func_from_nested_chats(chat_queue, recipient, messages, sender, config) + + functools.update_wrapper(wrapped_reply_func, reply_func_from_nested_chats) + self.register_reply( trigger, - reply_func, + wrapped_reply_func, position, kwargs.get("config"), kwargs.get("reset_config"), @@ -564,6 +586,11 @@ def _append_oai_message(self, message: Union[Dict, str], role, conversation_id: if message.get("role") in ["function", "tool"]: oai_message["role"] = message.get("role") + elif "override_role" in message: + # If we have a direction to override the role then set the + # role accordingly. Used to customise the role for the + # select speaker prompt. + oai_message["role"] = message.get("override_role") else: oai_message["role"] = role @@ -745,6 +772,9 @@ def _print_received_message(self, message: Union[Dict, str], sender: Agent): def _process_received_message(self, message: Union[Dict, str], sender: Agent, silent: bool): # When the agent receives a message, the role of the message is "user". (If 'role' exists and is 'function', it will remain unchanged.) valid = self._append_oai_message(message, "user", sender) + if logging_enabled(): + log_event(self, "received_message", message=message, sender=sender.name, valid=valid) + if not valid: raise ValueError( "Received message can't be converted into a valid ChatCompletion message. Either content or function_call must be provided." @@ -907,6 +937,7 @@ def my_summary_method( One example key is "summary_prompt", and value is a string of text used to prompt a LLM-based agent (the sender or receiver agent) to reflect on the conversation and extract a summary when summary_method is "reflection_with_llm". The default summary_prompt is DEFAULT_SUMMARY_PROMPT, i.e., "Summarize takeaway from the conversation. Do not add any introductory phrases. If the intended request is NOT properly addressed, please point it out." + Another available key is "summary_role", which is the role of the message sent to the agent in charge of summarizing. Default is "system". message (str, dict or Callable): the initial message to be sent to the recipient. Needs to be provided. Otherwise, input() will be called to get the initial message. - If a string or a dict is provided, it will be used as the initial message. `generate_init_message` is called to generate the initial message for the agent based on this string and the context. If dict, it may contain the following reserved fields (either content or tool_calls need to be provided). @@ -1116,11 +1147,18 @@ def my_summary_method( @staticmethod def _last_msg_as_summary(sender, recipient, summary_args) -> str: """Get a chat summary from the last message of the recipient.""" + summary = "" try: - summary = recipient.last_message(sender)["content"].replace("TERMINATE", "") + content = recipient.last_message(sender)["content"] + if isinstance(content, str): + summary = content.replace("TERMINATE", "") + elif isinstance(content, list): + # Remove the `TERMINATE` word in the content list. + summary = "\n".join( + x["text"].replace("TERMINATE", "") for x in content if isinstance(x, dict) and "text" in x + ) except (IndexError, AttributeError) as e: warnings.warn(f"Cannot extract summary using last_msg: {e}. Using an empty str as summary.", UserWarning) - summary = "" return summary @staticmethod @@ -1131,8 +1169,13 @@ def _reflection_with_llm_as_summary(sender, recipient, summary_args): raise ValueError("The summary_prompt must be a string.") msg_list = recipient.chat_messages_for_summary(sender) agent = sender if recipient is None else recipient + role = summary_args.get("summary_role", None) + if role and not isinstance(role, str): + raise ValueError("The summary_role in summary_arg must be a string.") try: - summary = sender._reflection_with_llm(prompt, msg_list, llm_agent=agent, cache=summary_args.get("cache")) + summary = sender._reflection_with_llm( + prompt, msg_list, llm_agent=agent, cache=summary_args.get("cache"), role=role + ) except BadRequestError as e: warnings.warn( f"Cannot extract summary using reflection_with_llm: {e}. Using an empty str as summary.", UserWarning @@ -1141,7 +1184,12 @@ def _reflection_with_llm_as_summary(sender, recipient, summary_args): return summary def _reflection_with_llm( - self, prompt, messages, llm_agent: Optional[Agent] = None, cache: Optional[AbstractCache] = None + self, + prompt, + messages, + llm_agent: Optional[Agent] = None, + cache: Optional[AbstractCache] = None, + role: Union[str, None] = None, ) -> str: """Get a chat summary using reflection with an llm client based on the conversation history. @@ -1150,10 +1198,14 @@ def _reflection_with_llm( messages (list): The messages generated as part of a chat conversation. llm_agent: the agent with an llm client. cache (AbstractCache or None): the cache client to be used for this conversation. + role (str): the role of the message, usually "system" or "user". Default is "system". """ + if not role: + role = "system" + system_msg = [ { - "role": "system", + "role": role, "content": prompt, } ] @@ -1168,6 +1220,23 @@ def _reflection_with_llm( response = self._generate_oai_reply_from_client(llm_client=llm_client, messages=messages, cache=cache) return response + def _check_chat_queue_for_sender(self, chat_queue: List[Dict[str, Any]]) -> List[Dict[str, Any]]: + """ + Check the chat queue and add the "sender" key if it's missing. + + Args: + chat_queue (List[Dict[str, Any]]): A list of dictionaries containing chat information. + + Returns: + List[Dict[str, Any]]: A new list of dictionaries with the "sender" key added if it was missing. + """ + chat_queue_with_sender = [] + for chat_info in chat_queue: + if chat_info.get("sender") is None: + chat_info["sender"] = self + chat_queue_with_sender.append(chat_info) + return chat_queue_with_sender + def initiate_chats(self, chat_queue: List[Dict[str, Any]]) -> List[ChatResult]: """(Experimental) Initiate chats with multiple agents. @@ -1177,16 +1246,12 @@ def initiate_chats(self, chat_queue: List[Dict[str, Any]]) -> List[ChatResult]: Returns: a list of ChatResult objects corresponding to the finished chats in the chat_queue. """ - _chat_queue = chat_queue.copy() - for chat_info in _chat_queue: - chat_info["sender"] = self + _chat_queue = self._check_chat_queue_for_sender(chat_queue) self._finished_chats = initiate_chats(_chat_queue) return self._finished_chats async def a_initiate_chats(self, chat_queue: List[Dict[str, Any]]) -> Dict[int, ChatResult]: - _chat_queue = chat_queue.copy() - for chat_info in _chat_queue: - chat_info["sender"] = self + _chat_queue = self._check_chat_queue_for_sender(chat_queue) self._finished_chats = await a_initiate_chats(_chat_queue) return self._finished_chats @@ -1299,7 +1364,7 @@ def _generate_oai_reply_from_client(self, llm_client, messages, cache) -> Union[ extracted_response = llm_client.extract_text_or_completion_object(response)[0] if extracted_response is None: - warnings.warn("Extracted_response from {response} is None.", UserWarning) + warnings.warn(f"Extracted_response from {response} is None.", UserWarning) return None # ensure function and tool calls will be accepted when sent back to the LLM if not isinstance(extracted_response, str) and hasattr(extracted_response, "model_dump"): @@ -1907,6 +1972,15 @@ def generate_reply( continue if self._match_trigger(reply_func_tuple["trigger"], sender): final, reply = reply_func(self, messages=messages, sender=sender, config=reply_func_tuple["config"]) + if logging_enabled(): + log_event( + self, + "reply_func_executed", + reply_func_module=reply_func.__module__, + reply_func_name=reply_func.__name__, + final=final, + reply=reply, + ) if final: return reply return self._default_auto_reply @@ -2076,7 +2150,7 @@ def execute_code_blocks(self, code_blocks): ) if lang in ["bash", "shell", "sh"]: exitcode, logs, image = self.run_code(code, lang=lang, **self._code_execution_config) - elif lang in ["python", "Python"]: + elif lang in PYTHON_VARIANTS: if code.startswith("# filename: "): filename = code[11 : code.find("\n")].strip() else: @@ -2259,30 +2333,54 @@ def generate_init_message(self, message: Union[Dict, str, None], **kwargs) -> Un """ if message is None: message = self.get_human_input(">") + + return self._handle_carryover(message, kwargs) + + def _handle_carryover(self, message: Union[str, Dict], kwargs: dict) -> Union[str, Dict]: + if not kwargs.get("carryover"): + return message + if isinstance(message, str): return self._process_carryover(message, kwargs) + elif isinstance(message, dict): - message = message.copy() - # TODO: Do we need to do the following? - # if message.get("content") is None: - # message["content"] = self.get_human_input(">") - message["content"] = self._process_carryover(message.get("content", ""), kwargs) - return message + if isinstance(message.get("content"), str): + # Makes sure the original message is not mutated + message = message.copy() + message["content"] = self._process_carryover(message["content"], kwargs) + elif isinstance(message.get("content"), list): + # Makes sure the original message is not mutated + message = message.copy() + message["content"] = self._process_multimodal_carryover(message["content"], kwargs) + else: + raise InvalidCarryOverType("Carryover should be a string or a list of strings.") - def _process_carryover(self, message: str, kwargs: dict) -> str: - carryover = kwargs.get("carryover") - if carryover: - # if carryover is string - if isinstance(carryover, str): - message += "\nContext: \n" + carryover - elif isinstance(carryover, list): - message += "\nContext: \n" + ("\n").join([t for t in carryover]) - else: - raise InvalidCarryOverType( - "Carryover should be a string or a list of strings. Not adding carryover to the message." - ) return message + def _process_carryover(self, content: str, kwargs: dict) -> str: + # Makes sure there's a carryover + if not kwargs.get("carryover"): + return content + + # if carryover is string + if isinstance(kwargs["carryover"], str): + content += "\nContext: \n" + kwargs["carryover"] + elif isinstance(kwargs["carryover"], list): + content += "\nContext: \n" + ("\n").join([t for t in kwargs["carryover"]]) + else: + raise InvalidCarryOverType( + "Carryover should be a string or a list of strings. Not adding carryover to the message." + ) + return content + + def _process_multimodal_carryover(self, content: List[Dict], kwargs: dict) -> List[Dict]: + """Prepends the context to a multimodal message.""" + # Makes sure there's a carryover + if not kwargs.get("carryover"): + return content + + return [{"type": "text", "text": self._process_carryover("", kwargs)}] + content + async def a_generate_init_message(self, message: Union[Dict, str, None], **kwargs) -> Union[str, Dict]: """Generate the initial message for the agent. If message is None, input() will be called to get the initial message. @@ -2295,12 +2393,8 @@ async def a_generate_init_message(self, message: Union[Dict, str, None], **kwarg """ if message is None: message = await self.a_get_human_input(">") - if isinstance(message, str): - return self._process_carryover(message, kwargs) - elif isinstance(message, dict): - message = message.copy() - message["content"] = self._process_carryover(message["content"], kwargs) - return message + + return self._handle_carryover(message, kwargs) def register_function(self, function_map: Dict[str, Union[Callable, None]]): """Register functions to the agent. diff --git a/autogen/agentchat/groupchat.py b/autogen/agentchat/groupchat.py index 6c0ecec90fe..b1141cfdacf 100644 --- a/autogen/agentchat/groupchat.py +++ b/autogen/agentchat/groupchat.py @@ -1,3 +1,5 @@ +import copy +import json import logging import random import re @@ -5,13 +7,15 @@ from dataclasses import dataclass, field from typing import Callable, Dict, List, Literal, Optional, Tuple, Union -from .agent import Agent -from .conversable_agent import ConversableAgent -from ..io.base import IOStream from ..code_utils import content_str from ..exception_utils import AgentNameConflict, NoEligibleSpeaker, UndefinedNextAgent +from ..formatting_utils import colored from ..graph_utils import check_graph_validity, invert_disallowed_to_allowed +from ..io.base import IOStream from ..runtime_logging import log_new_agent, logging_enabled +from .agent import Agent +from .chat import ChatResult +from .conversable_agent import ConversableAgent logger = logging.getLogger(__name__) @@ -28,6 +32,28 @@ class GroupChat: When set to True and when a message is a function call suggestion, the next speaker will be chosen from an agent which contains the corresponding function name in its `function_map`. + - select_speaker_message_template: customize the select speaker message (used in "auto" speaker selection), which appears first in the message context and generally includes the agent descriptions and list of agents. If the string contains "{roles}" it will replaced with the agent's and their role descriptions. If the string contains "{agentlist}" it will be replaced with a comma-separated list of agent names in square brackets. The default value is: + "You are in a role play game. The following roles are available: + {roles}. + Read the following conversation. + Then select the next role from {agentlist} to play. Only return the role." + - select_speaker_prompt_template: customize the select speaker prompt (used in "auto" speaker selection), which appears last in the message context and generally includes the list of agents and guidance for the LLM to select the next agent. If the string contains "{agentlist}" it will be replaced with a comma-separated list of agent names in square brackets. The default value is: + "Read the above conversation. Then select the next role from {agentlist} to play. Only return the role." + - select_speaker_auto_multiple_template: customize the follow-up prompt used when selecting a speaker fails with a response that contains multiple agent names. This prompt guides the LLM to return just one agent name. Applies only to "auto" speaker selection method. If the string contains "{agentlist}" it will be replaced with a comma-separated list of agent names in square brackets. The default value is: + "You provided more than one name in your text, please return just the name of the next speaker. To determine the speaker use these prioritised rules: + 1. If the context refers to themselves as a speaker e.g. "As the..." , choose that speaker's name + 2. If it refers to the "next" speaker name, choose that name + 3. Otherwise, choose the first provided speaker's name in the context + The names are case-sensitive and should not be abbreviated or changed. + Respond with ONLY the name of the speaker and DO NOT provide a reason." + - select_speaker_auto_none_template: customize the follow-up prompt used when selecting a speaker fails with a response that contains no agent names. This prompt guides the LLM to return an agent name and provides a list of agent names. Applies only to "auto" speaker selection method. If the string contains "{agentlist}" it will be replaced with a comma-separated list of agent names in square brackets. The default value is: + "You didn't choose a speaker. As a reminder, to determine the speaker use these prioritised rules: + 1. If the context refers to themselves as a speaker e.g. "As the..." , choose that speaker's name + 2. If it refers to the "next" speaker name, choose that name + 3. Otherwise, choose the first provided speaker's name in the context + The names are case-sensitive and should not be abbreviated or changed. + The only names that are accepted are {agentlist}. + Respond with ONLY the name of the speaker and DO NOT provide a reason." - speaker_selection_method: the method for selecting the next speaker. Default is "auto". Could be any of the following (case insensitive), will raise ValueError if not recognized: - "auto": the next speaker is selected automatically by LLM. @@ -44,6 +70,15 @@ def custom_speaker_selection_func( last_speaker: Agent, groupchat: GroupChat ) -> Union[Agent, str, None]: ``` + - max_retries_for_selecting_speaker: the maximum number of times the speaker selection requery process will run. + If, during speaker selection, multiple agent names or no agent names are returned by the LLM as the next agent, it will be queried again up to the maximum number + of times until a single agent is returned or it exhausts the maximum attempts. + Applies only to "auto" speaker selection method. + Default is 2. + - select_speaker_auto_verbose: whether to output the select speaker responses and selections + If set to True, the outputs from the two agents in the nested select speaker chat will be output, along with + whether the responses were successful, or not, in selecting an agent + Applies only to "auto" speaker selection method. - allow_repeat_speaker: whether to allow the same speaker to speak consecutively. Default is True, in which case all speakers are allowed to speak consecutively. If `allow_repeat_speaker` is a list of Agents, then only those listed agents are allowed to repeat. @@ -61,6 +96,7 @@ def custom_speaker_selection_func( "clear history" phrase in user prompt. This is experimental feature. See description of GroupChatManager.clear_agents_history function for more info. - send_introductions: send a round of introductions at the start of the group chat, so agents know who they can speak to (default: False) + - role_for_select_speaker_messages: sets the role name for speaker selection when in 'auto' mode, typically 'user' or 'system'. (default: 'system') """ agents: List[Agent] @@ -69,11 +105,34 @@ def custom_speaker_selection_func( admin_name: Optional[str] = "Admin" func_call_filter: Optional[bool] = True speaker_selection_method: Union[Literal["auto", "manual", "random", "round_robin"], Callable] = "auto" + max_retries_for_selecting_speaker: Optional[int] = 2 allow_repeat_speaker: Optional[Union[bool, List[Agent]]] = None allowed_or_disallowed_speaker_transitions: Optional[Dict] = None speaker_transitions_type: Literal["allowed", "disallowed", None] = None enable_clear_history: Optional[bool] = False send_introductions: bool = False + select_speaker_message_template: str = """You are in a role play game. The following roles are available: + {roles}. + Read the following conversation. + Then select the next role from {agentlist} to play. Only return the role.""" + select_speaker_prompt_template: str = ( + "Read the above conversation. Then select the next role from {agentlist} to play. Only return the role." + ) + select_speaker_auto_multiple_template: str = """You provided more than one name in your text, please return just the name of the next speaker. To determine the speaker use these prioritised rules: + 1. If the context refers to themselves as a speaker e.g. "As the..." , choose that speaker's name + 2. If it refers to the "next" speaker name, choose that name + 3. Otherwise, choose the first provided speaker's name in the context + The names are case-sensitive and should not be abbreviated or changed. + Respond with ONLY the name of the speaker and DO NOT provide a reason.""" + select_speaker_auto_none_template: str = """You didn't choose a speaker. As a reminder, to determine the speaker use these prioritised rules: + 1. If the context refers to themselves as a speaker e.g. "As the..." , choose that speaker's name + 2. If it refers to the "next" speaker name, choose that name + 3. Otherwise, choose the first provided speaker's name in the context + The names are case-sensitive and should not be abbreviated or changed. + The only names that are accepted are {agentlist}. + Respond with ONLY the name of the speaker and DO NOT provide a reason.""" + select_speaker_auto_verbose: Optional[bool] = False + role_for_select_speaker_messages: Optional[str] = "system" _VALID_SPEAKER_SELECTION_METHODS = ["auto", "manual", "random", "round_robin"] _VALID_SPEAKER_TRANSITIONS_TYPE = ["allowed", "disallowed", None] @@ -162,6 +221,37 @@ def __post_init__(self): agents=self.agents, ) + # Check select speaker messages, prompts, roles, and retries have values + if self.select_speaker_message_template is None or len(self.select_speaker_message_template) == 0: + raise ValueError("select_speaker_message_template cannot be empty or None.") + + if self.select_speaker_prompt_template is None or len(self.select_speaker_prompt_template) == 0: + raise ValueError("select_speaker_prompt_template cannot be empty or None.") + + if self.role_for_select_speaker_messages is None or len(self.role_for_select_speaker_messages) == 0: + raise ValueError("role_for_select_speaker_messages cannot be empty or None.") + + if self.select_speaker_auto_multiple_template is None or len(self.select_speaker_auto_multiple_template) == 0: + raise ValueError("select_speaker_auto_multiple_template cannot be empty or None.") + + if self.select_speaker_auto_none_template is None or len(self.select_speaker_auto_none_template) == 0: + raise ValueError("select_speaker_auto_none_template cannot be empty or None.") + + if self.max_retries_for_selecting_speaker is None or len(self.role_for_select_speaker_messages) == 0: + raise ValueError("role_for_select_speaker_messages cannot be empty or None.") + + # Validate max select speakers retries + if self.max_retries_for_selecting_speaker is None or not isinstance( + self.max_retries_for_selecting_speaker, int + ): + raise ValueError("max_retries_for_selecting_speaker cannot be None or non-int") + elif self.max_retries_for_selecting_speaker < 0: + raise ValueError("max_retries_for_selecting_speaker must be greater than or equal to zero") + + # Validate select_speaker_auto_verbose + if self.select_speaker_auto_verbose is None or not isinstance(self.select_speaker_auto_verbose, bool): + raise ValueError("select_speaker_auto_verbose cannot be None or non-bool") + @property def agent_names(self) -> List[str]: """Return the names of the agents in the group chat.""" @@ -232,17 +322,22 @@ def select_speaker_msg(self, agents: Optional[List[Agent]] = None) -> str: """Return the system message for selecting the next speaker. This is always the *first* message in the context.""" if agents is None: agents = self.agents - return f"""You are in a role play game. The following roles are available: -{self._participant_roles(agents)}. -Read the following conversation. -Then select the next role from {[agent.name for agent in agents]} to play. Only return the role.""" + roles = self._participant_roles(agents) + agentlist = f"{[agent.name for agent in agents]}" + + return_msg = self.select_speaker_message_template.format(roles=roles, agentlist=agentlist) + return return_msg def select_speaker_prompt(self, agents: Optional[List[Agent]] = None) -> str: """Return the floating system prompt selecting the next speaker. This is always the *last* message in the context.""" if agents is None: agents = self.agents - return f"Read the above conversation. Then select the next role from {[agent.name for agent in agents]} to play. Only return the role." + + agentlist = f"{[agent.name for agent in agents]}" + + return_prompt = self.select_speaker_prompt_template.format(agentlist=agentlist) + return return_prompt def introductions_msg(self, agents: Optional[List[Agent]] = None) -> str: """Return the system message for selecting the next speaker. This is always the *first* message in the context.""" @@ -411,7 +506,7 @@ def _prepare_and_select_agents( selected_agent = self.next_agent(last_speaker, graph_eligible_agents) elif speaker_selection_method.lower() == "random": selected_agent = self.random_select_speaker(graph_eligible_agents) - else: + else: # auto selected_agent = None select_speaker_messages = self.messages.copy() # If last message is a tool call or function call, blank the call so the api doesn't throw @@ -419,30 +514,34 @@ def _prepare_and_select_agents( select_speaker_messages[-1] = dict(select_speaker_messages[-1], function_call=None) if select_speaker_messages[-1].get("tool_calls", False): select_speaker_messages[-1] = dict(select_speaker_messages[-1], tool_calls=None) - select_speaker_messages = select_speaker_messages + [ - {"role": "system", "content": self.select_speaker_prompt(graph_eligible_agents)} - ] return selected_agent, graph_eligible_agents, select_speaker_messages def select_speaker(self, last_speaker: Agent, selector: ConversableAgent) -> Agent: - """Select the next speaker.""" + """Select the next speaker (with requery).""" + + # Prepare the list of available agents and select an agent if selection method allows (non-auto) selected_agent, agents, messages = self._prepare_and_select_agents(last_speaker) if selected_agent: return selected_agent - # auto speaker selection - selector.update_system_message(self.select_speaker_msg(agents)) - final, name = selector.generate_oai_reply(messages) - return self._finalize_speaker(last_speaker, final, name, agents) + elif self.speaker_selection_method == "manual": + # An agent has not been selected while in manual mode, so move to the next agent + return self.next_agent(last_speaker) + + # auto speaker selection with 2-agent chat + return self._auto_select_speaker(last_speaker, selector, messages, agents) async def a_select_speaker(self, last_speaker: Agent, selector: ConversableAgent) -> Agent: - """Select the next speaker.""" + """Select the next speaker (with requery), asynchronously.""" + selected_agent, agents, messages = self._prepare_and_select_agents(last_speaker) if selected_agent: return selected_agent - # auto speaker selection - selector.update_system_message(self.select_speaker_msg(agents)) - final, name = await selector.a_generate_oai_reply(messages) - return self._finalize_speaker(last_speaker, final, name, agents) + elif self.speaker_selection_method == "manual": + # An agent has not been selected while in manual mode, so move to the next agent + return self.next_agent(last_speaker) + + # auto speaker selection with 2-agent chat + return await self.a_auto_select_speaker(last_speaker, selector, messages, agents) def _finalize_speaker(self, last_speaker: Agent, final: bool, name: str, agents: Optional[List[Agent]]) -> Agent: if not final: @@ -462,6 +561,296 @@ def _finalize_speaker(self, last_speaker: Agent, final: bool, name: str, agents: agent = self.agent_by_name(name) return agent if agent else self.next_agent(last_speaker, agents) + def _auto_select_speaker( + self, + last_speaker: Agent, + selector: ConversableAgent, + messages: Optional[List[Dict]], + agents: Optional[List[Agent]], + ) -> Agent: + """Selects next speaker for the "auto" speaker selection method. Utilises its own two-agent chat to determine the next speaker and supports requerying. + + Speaker selection for "auto" speaker selection method: + 1. Create a two-agent chat with a speaker selector agent and a speaker validator agent, like a nested chat + 2. Inject the group messages into the new chat + 3. Run the two-agent chat, evaluating the result of response from the speaker selector agent: + - If a single agent is provided then we return it and finish. If not, we add an additional message to this nested chat in an attempt to guide the LLM to a single agent response + 4. Chat continues until a single agent is nominated or there are no more attempts left + 5. If we run out of turns and no single agent can be determined, the next speaker in the list of agents is returned + + Args: + last_speaker Agent: The previous speaker in the group chat + selector ConversableAgent: + messages Optional[List[Dict]]: Current chat messages + agents Optional[List[Agent]]: Valid list of agents for speaker selection + + Returns: + Dict: a counter for mentioned agents. + """ + + # If no agents are passed in, assign all the group chat's agents + if agents is None: + agents = self.agents + + # The maximum number of speaker selection attempts (including requeries) + # is the initial speaker selection attempt plus the maximum number of retries. + # We track these and use them in the validation function as we can't + # access the max_turns from within validate_speaker_name. + max_attempts = 1 + self.max_retries_for_selecting_speaker + attempts_left = max_attempts + attempt = 0 + + # Registered reply function for checking_agent, checks the result of the response for agent names + def validate_speaker_name(recipient, messages, sender, config) -> Tuple[bool, Union[str, Dict, None]]: + + # The number of retries left, starting at max_retries_for_selecting_speaker + nonlocal attempts_left + nonlocal attempt + + attempt = attempt + 1 + attempts_left = attempts_left - 1 + + return self._validate_speaker_name(recipient, messages, sender, config, attempts_left, attempt, agents) + + # Two-agent chat for speaker selection + + # Agent for checking the response from the speaker_select_agent + checking_agent = ConversableAgent("checking_agent", default_auto_reply=max_attempts) + + # Register the speaker validation function with the checking agent + checking_agent.register_reply( + [ConversableAgent, None], + reply_func=validate_speaker_name, # Validate each response + remove_other_reply_funcs=True, + ) + + # Agent for selecting a single agent name from the response + speaker_selection_agent = ConversableAgent( + "speaker_selection_agent", + system_message=self.select_speaker_msg(agents), + chat_messages={checking_agent: messages}, + llm_config=selector.llm_config, + human_input_mode="NEVER", # Suppresses some extra terminal outputs, outputs will be handled by select_speaker_auto_verbose + ) + + # Run the speaker selection chat + result = checking_agent.initiate_chat( + speaker_selection_agent, + cache=None, # don't use caching for the speaker selection chat + message={ + "content": self.select_speaker_prompt(agents), + "override_role": self.role_for_select_speaker_messages, + }, + max_turns=2 + * max(1, max_attempts), # Limiting the chat to the number of attempts, including the initial one + clear_history=False, + silent=not self.select_speaker_auto_verbose, # Base silence on the verbose attribute + ) + + return self._process_speaker_selection_result(result, last_speaker, agents) + + async def a_auto_select_speaker( + self, + last_speaker: Agent, + selector: ConversableAgent, + messages: Optional[List[Dict]], + agents: Optional[List[Agent]], + ) -> Agent: + """(Asynchronous) Selects next speaker for the "auto" speaker selection method. Utilises its own two-agent chat to determine the next speaker and supports requerying. + + Speaker selection for "auto" speaker selection method: + 1. Create a two-agent chat with a speaker selector agent and a speaker validator agent, like a nested chat + 2. Inject the group messages into the new chat + 3. Run the two-agent chat, evaluating the result of response from the speaker selector agent: + - If a single agent is provided then we return it and finish. If not, we add an additional message to this nested chat in an attempt to guide the LLM to a single agent response + 4. Chat continues until a single agent is nominated or there are no more attempts left + 5. If we run out of turns and no single agent can be determined, the next speaker in the list of agents is returned + + Args: + last_speaker Agent: The previous speaker in the group chat + selector ConversableAgent: + messages Optional[List[Dict]]: Current chat messages + agents Optional[List[Agent]]: Valid list of agents for speaker selection + + Returns: + Dict: a counter for mentioned agents. + """ + + # If no agents are passed in, assign all the group chat's agents + if agents is None: + agents = self.agents + + # The maximum number of speaker selection attempts (including requeries) + # We track these and use them in the validation function as we can't + # access the max_turns from within validate_speaker_name + max_attempts = 1 + self.max_retries_for_selecting_speaker + attempts_left = max_attempts + attempt = 0 + + # Registered reply function for checking_agent, checks the result of the response for agent names + def validate_speaker_name(recipient, messages, sender, config) -> Tuple[bool, Union[str, Dict, None]]: + + # The number of retries left, starting at max_retries_for_selecting_speaker + nonlocal attempts_left + nonlocal attempt + + attempt = attempt + 1 + attempts_left = attempts_left - 1 + + return self._validate_speaker_name(recipient, messages, sender, config, attempts_left, attempt, agents) + + # Two-agent chat for speaker selection + + # Agent for checking the response from the speaker_select_agent + checking_agent = ConversableAgent("checking_agent", default_auto_reply=max_attempts) + + # Register the speaker validation function with the checking agent + checking_agent.register_reply( + [ConversableAgent, None], + reply_func=validate_speaker_name, # Validate each response + remove_other_reply_funcs=True, + ) + + # Agent for selecting a single agent name from the response + speaker_selection_agent = ConversableAgent( + "speaker_selection_agent", + system_message=self.select_speaker_msg(agents), + chat_messages={checking_agent: messages}, + llm_config=selector.llm_config, + human_input_mode="NEVER", # Suppresses some extra terminal outputs, outputs will be handled by select_speaker_auto_verbose + ) + + # Run the speaker selection chat + result = await checking_agent.a_initiate_chat( + speaker_selection_agent, + cache=None, # don't use caching for the speaker selection chat + message=self.select_speaker_prompt(agents), + max_turns=2 + * max(1, max_attempts), # Limiting the chat to the number of attempts, including the initial one + clear_history=False, + silent=not self.select_speaker_auto_verbose, # Base silence on the verbose attribute + ) + + return self._process_speaker_selection_result(result, last_speaker, agents) + + def _validate_speaker_name( + self, recipient, messages, sender, config, attempts_left, attempt, agents + ) -> Tuple[bool, Union[str, Dict, None]]: + """Validates the speaker response for each round in the internal 2-agent + chat within the auto select speaker method. + + Used by auto_select_speaker and a_auto_select_speaker. + """ + + # Output the query and requery results + if self.select_speaker_auto_verbose: + iostream = IOStream.get_default() + + # Validate the speaker name selected + select_name = messages[-1]["content"].strip() + + mentions = self._mentioned_agents(select_name, agents) + + if len(mentions) == 1: + + # Success on retry, we have just one name mentioned + selected_agent_name = next(iter(mentions)) + + # Add the selected agent to the response so we can return it + messages.append({"role": "user", "content": f"[AGENT SELECTED]{selected_agent_name}"}) + + if self.select_speaker_auto_verbose: + iostream.print( + colored( + f">>>>>>>> Select speaker attempt {attempt} of {attempt + attempts_left} successfully selected: {selected_agent_name}", + "green", + ), + flush=True, + ) + + elif len(mentions) > 1: + # More than one name on requery so add additional reminder prompt for next retry + + if self.select_speaker_auto_verbose: + iostream.print( + colored( + f">>>>>>>> Select speaker attempt {attempt} of {attempt + attempts_left} failed as it included multiple agent names.", + "red", + ), + flush=True, + ) + + if attempts_left: + # Message to return to the chat for the next attempt + agentlist = f"{[agent.name for agent in agents]}" + + return True, { + "content": self.select_speaker_auto_multiple_template.format(agentlist=agentlist), + "override_role": self.role_for_select_speaker_messages, + } + else: + # Final failure, no attempts left + messages.append( + { + "role": "user", + "content": f"[AGENT SELECTION FAILED]Select speaker attempt #{attempt} of {attempt + attempts_left} failed as it returned multiple names.", + } + ) + + else: + # No names at all on requery so add additional reminder prompt for next retry + + if self.select_speaker_auto_verbose: + iostream.print( + colored( + f">>>>>>>> Select speaker attempt #{attempt} failed as it did not include any agent names.", + "red", + ), + flush=True, + ) + + if attempts_left: + # Message to return to the chat for the next attempt + agentlist = f"{[agent.name for agent in agents]}" + + return True, { + "content": self.select_speaker_auto_none_template.format(agentlist=agentlist), + "override_role": self.role_for_select_speaker_messages, + } + else: + # Final failure, no attempts left + messages.append( + { + "role": "user", + "content": f"[AGENT SELECTION FAILED]Select speaker attempt #{attempt} of {attempt + attempts_left} failed as it did not include any agent names.", + } + ) + + return True, None + + def _process_speaker_selection_result(self, result, last_speaker: ConversableAgent, agents: Optional[List[Agent]]): + """Checks the result of the auto_select_speaker function, returning the + agent to speak. + + Used by auto_select_speaker and a_auto_select_speaker.""" + if len(result.chat_history) > 0: + + # Use the final message, which will have the selected agent or reason for failure + final_message = result.chat_history[-1]["content"] + + if "[AGENT SELECTED]" in final_message: + + # Have successfully selected an agent, return it + return self.agent_by_name(final_message.replace("[AGENT SELECTED]", "")) + + else: # "[AGENT SELECTION FAILED]" + + # Failed to select an agent, so we'll select the next agent in the list + next_agent = self.next_agent(last_speaker, agents) + + # No agent, return the failed reason + return next_agent + def _participant_roles(self, agents: List[Agent] = None) -> str: # Default to all agents registered if agents is None: @@ -478,6 +867,10 @@ def _participant_roles(self, agents: List[Agent] = None) -> str: def _mentioned_agents(self, message_content: Union[str, List], agents: Optional[List[Agent]]) -> Dict: """Counts the number of times each agent is mentioned in the provided message content. + Agent names will match under any of the following conditions (all case-sensitive): + - Exact name match + - If the agent name has underscores it will match with spaces instead (e.g. 'Story_writer' == 'Story writer') + - If the agent name has underscores it will match with '\\_' instead of '_' (e.g. 'Story_writer' == 'Story\\_writer') Args: message_content (Union[str, List]): The content of the message, either as a single string or a list of strings. @@ -496,9 +889,17 @@ def _mentioned_agents(self, message_content: Union[str, List], agents: Optional[ mentions = dict() for agent in agents: + # Finds agent mentions, taking word boundaries into account, + # accommodates escaping underscores and underscores as spaces regex = ( - r"(?<=\W)" + re.escape(agent.name) + r"(?=\W)" - ) # Finds agent mentions, taking word boundaries into account + r"(?<=\W)(" + + re.escape(agent.name) + + r"|" + + re.escape(agent.name.replace("_", " ")) + + r"|" + + re.escape(agent.name.replace("_", r"\_")) + + r")(?=\W)" + ) count = len(re.findall(regex, f" {message_content} ")) # Pad the message to help with matching if count > 0: mentions[agent.name] = count @@ -718,6 +1119,290 @@ async def a_run_chat( a.previous_cache = None return True, None + def resume( + self, + messages: Union[List[Dict], str], + remove_termination_string: str = None, + silent: Optional[bool] = False, + ) -> Tuple[ConversableAgent, Dict]: + """Resumes a group chat using the previous messages as a starting point. Requires the agents, group chat, and group chat manager to be established + as per the original group chat. + + Args: + - messages Union[List[Dict], str]: The content of the previous chat's messages, either as a Json string or a list of message dictionaries. + - remove_termination_string str: Remove the provided string from the last message to prevent immediate termination + - silent (bool or None): (Experimental) whether to print the messages for this conversation. Default is False. + + Returns: + - Tuple[ConversableAgent, Dict]: A tuple containing the last agent who spoke and their message + """ + + # Convert messages from string to messages list, if needed + if isinstance(messages, str): + messages = self.messages_from_string(messages) + elif isinstance(messages, list) and all(isinstance(item, dict) for item in messages): + messages = copy.deepcopy(messages) + else: + raise Exception("Messages is not of type str or List[Dict]") + + # Clean up the objects, ensuring there are no messages in the agents and group chat + + # Clear agent message history + for agent in self._groupchat.agents: + if isinstance(agent, ConversableAgent): + agent.clear_history() + + # Clear Manager message history + self.clear_history() + + # Clear GroupChat messages + self._groupchat.reset() + + # Validation of message and agents + + try: + self._valid_resume_messages(messages) + except: + raise + + # Load the messages into the group chat + for i, message in enumerate(messages): + + if "name" in message: + message_speaker_agent = self._groupchat.agent_by_name(message["name"]) + else: + # If there's no name, assign the group chat manager (this is an indication the ChatResult messages was used instead of groupchat.messages as state) + message_speaker_agent = self + message["name"] = self.name + + # If it wasn't an agent speaking, it may be the manager + if not message_speaker_agent and message["name"] == self.name: + message_speaker_agent = self + + # Add previous messages to each agent (except their own messages and the last message, as we'll kick off the conversation with it) + if i != len(messages) - 1: + for agent in self._groupchat.agents: + if agent.name != message["name"]: + self.send(message, self._groupchat.agent_by_name(agent.name), request_reply=False, silent=True) + + # Add previous message to the new groupchat, if it's an admin message the name may not match so add the message directly + if message_speaker_agent: + self._groupchat.append(message, message_speaker_agent) + else: + self._groupchat.messages.append(message) + + # Last speaker agent + last_speaker_name = message["name"] + + # Last message to check for termination (we could avoid this by ignoring termination check for resume in the future) + last_message = message + + # Get last speaker as an agent + previous_last_agent = self._groupchat.agent_by_name(name=last_speaker_name) + + # If we didn't match a last speaker agent, we check that it's the group chat's admin name and assign the manager, if so + if not previous_last_agent and ( + last_speaker_name == self._groupchat.admin_name or last_speaker_name == self.name + ): + previous_last_agent = self + + # Termination removal and check + self._process_resume_termination(remove_termination_string, messages) + + if not silent: + iostream = IOStream.get_default() + iostream.print( + f"Prepared group chat with {len(messages)} messages, the last speaker is", + colored(last_speaker_name, "yellow"), + flush=True, + ) + + # Update group chat settings for resuming + self._groupchat.send_introductions = False + + return previous_last_agent, last_message + + async def a_resume( + self, + messages: Union[List[Dict], str], + remove_termination_string: str = None, + silent: Optional[bool] = False, + ) -> Tuple[ConversableAgent, Dict]: + """Resumes a group chat using the previous messages as a starting point, asynchronously. Requires the agents, group chat, and group chat manager to be established + as per the original group chat. + + Args: + - messages Union[List[Dict], str]: The content of the previous chat's messages, either as a Json string or a list of message dictionaries. + - remove_termination_string str: Remove the provided string from the last message to prevent immediate termination + - silent (bool or None): (Experimental) whether to print the messages for this conversation. Default is False. + + Returns: + - Tuple[ConversableAgent, Dict]: A tuple containing the last agent who spoke and their message + """ + + # Convert messages from string to messages list, if needed + if isinstance(messages, str): + messages = self.messages_from_string(messages) + elif isinstance(messages, list) and all(isinstance(item, dict) for item in messages): + messages = copy.deepcopy(messages) + else: + raise Exception("Messages is not of type str or List[Dict]") + + # Clean up the objects, ensuring there are no messages in the agents and group chat + + # Clear agent message history + for agent in self._groupchat.agents: + if isinstance(agent, ConversableAgent): + agent.clear_history() + + # Clear Manager message history + self.clear_history() + + # Clear GroupChat messages + self._groupchat.reset() + + # Validation of message and agents + + try: + self._valid_resume_messages(messages) + except: + raise + + # Load the messages into the group chat + for i, message in enumerate(messages): + + if "name" in message: + message_speaker_agent = self._groupchat.agent_by_name(message["name"]) + else: + # If there's no name, assign the group chat manager (this is an indication the ChatResult messages was used instead of groupchat.messages as state) + message_speaker_agent = self + message["name"] = self.name + + # If it wasn't an agent speaking, it may be the manager + if not message_speaker_agent and message["name"] == self.name: + message_speaker_agent = self + + # Add previous messages to each agent (except their own messages and the last message, as we'll kick off the conversation with it) + if i != len(messages) - 1: + for agent in self._groupchat.agents: + if agent.name != message["name"]: + await self.a_send( + message, self._groupchat.agent_by_name(agent.name), request_reply=False, silent=True + ) + + # Add previous message to the new groupchat, if it's an admin message the name may not match so add the message directly + if message_speaker_agent: + self._groupchat.append(message, message_speaker_agent) + else: + self._groupchat.messages.append(message) + + # Last speaker agent + last_speaker_name = message["name"] + + # Last message to check for termination (we could avoid this by ignoring termination check for resume in the future) + last_message = message + + # Get last speaker as an agent + previous_last_agent = self._groupchat.agent_by_name(name=last_speaker_name) + + # If we didn't match a last speaker agent, we check that it's the group chat's admin name and assign the manager, if so + if not previous_last_agent and ( + last_speaker_name == self._groupchat.admin_name or last_speaker_name == self.name + ): + previous_last_agent = self + + # Termination removal and check + self._process_resume_termination(remove_termination_string, messages) + + if not silent: + iostream = IOStream.get_default() + iostream.print( + f"Prepared group chat with {len(messages)} messages, the last speaker is", + colored(last_speaker_name, "yellow"), + flush=True, + ) + + # Update group chat settings for resuming + self._groupchat.send_introductions = False + + return previous_last_agent, last_message + + def _valid_resume_messages(self, messages: List[Dict]): + """Validates the messages used for resuming + + args: + messages (List[Dict]): list of messages to resume with + + returns: + - bool: Whether they are valid for resuming + """ + # Must have messages to start with, otherwise they should run run_chat + if not messages: + raise Exception( + "Cannot resume group chat as no messages were provided. Use GroupChatManager.run_chat or ConversableAgent.initiate_chat to start a new chat." + ) + + # Check that all agents in the chat messages exist in the group chat + for message in messages: + if message.get("name"): + if ( + not self._groupchat.agent_by_name(message["name"]) + and not message["name"] == self._groupchat.admin_name # ignore group chat's name + and not message["name"] == self.name # ignore group chat manager's name + ): + raise Exception(f"Agent name in message doesn't exist as agent in group chat: {message['name']}") + + def _process_resume_termination(self, remove_termination_string: str, messages: List[Dict]): + """Removes termination string, if required, and checks if termination may occur. + + args: + remove_termination_string (str): termination string to remove from the last message + + returns: + None + """ + + last_message = messages[-1] + + # Replace any given termination string in the last message + if remove_termination_string: + if messages[-1].get("content") and remove_termination_string in messages[-1]["content"]: + messages[-1]["content"] = messages[-1]["content"].replace(remove_termination_string, "") + + # Check if the last message meets termination (if it has one) + if self._is_termination_msg: + if self._is_termination_msg(last_message): + logger.warning("WARNING: Last message meets termination criteria and this may terminate the chat.") + + def messages_from_string(self, message_string: str) -> List[Dict]: + """Reads the saved state of messages in Json format for resume and returns as a messages list + + args: + - message_string: Json string, the saved state + + returns: + - List[Dict]: List of messages + """ + try: + state = json.loads(message_string) + except json.JSONDecodeError: + raise Exception("Messages string is not a valid JSON string") + + return state + + def messages_to_string(self, messages: List[Dict]) -> str: + """Converts the provided messages into a Json string that can be used for resuming the chat. + The state is made up of a list of messages + + args: + - messages (List[Dict]): set of messages to convert to a string + + returns: + - str: Json representation of the messages which can be persisted for resuming later + """ + + return json.dumps(messages) + def _raise_exception_on_async_reply_functions(self) -> None: """Raise an exception if any async reply functions are registered. diff --git a/autogen/agentchat/user_proxy_agent.py b/autogen/agentchat/user_proxy_agent.py index d1d7f89ab2b..a80296a8355 100644 --- a/autogen/agentchat/user_proxy_agent.py +++ b/autogen/agentchat/user_proxy_agent.py @@ -1,7 +1,7 @@ from typing import Callable, Dict, List, Literal, Optional, Union +from ..runtime_logging import log_new_agent, logging_enabled from .conversable_agent import ConversableAgent -from ..runtime_logging import logging_enabled, log_new_agent class UserProxyAgent(ConversableAgent): diff --git a/autogen/agentchat/utils.py b/autogen/agentchat/utils.py index eef3741605d..b32c2f5f0a0 100644 --- a/autogen/agentchat/utils.py +++ b/autogen/agentchat/utils.py @@ -1,5 +1,5 @@ import re -from typing import Any, Callable, Dict, List, Tuple, Union +from typing import Any, Callable, Dict, List, Union from .agent import Agent @@ -26,33 +26,46 @@ def consolidate_chat_info(chat_info, uniform_sender=None) -> None: ), "llm client must be set in either the recipient or sender when summary_method is reflection_with_llm." -def gather_usage_summary(agents: List[Agent]) -> Tuple[Dict[str, any], Dict[str, any]]: +def gather_usage_summary(agents: List[Agent]) -> Dict[Dict[str, Dict], Dict[str, Dict]]: r"""Gather usage summary from all agents. Args: agents: (list): List of agents. Returns: - tuple: (total_usage_summary, actual_usage_summary) + dictionary: A dictionary containing two keys: + - "usage_including_cached_inference": Cost information on the total usage, including the tokens in cached inference. + - "usage_excluding_cached_inference": Cost information on the usage of tokens, excluding the tokens in cache. No larger than "usage_including_cached_inference". Example: ```python - total_usage_summary = { - "total_cost": 0.0006090000000000001, - "gpt-35-turbo": { - "cost": 0.0006090000000000001, - "prompt_tokens": 242, - "completion_tokens": 123, - "total_tokens": 365 + { + "usage_including_cached_inference" : { + "total_cost": 0.0006090000000000001, + "gpt-35-turbo": { + "cost": 0.0006090000000000001, + "prompt_tokens": 242, + "completion_tokens": 123, + "total_tokens": 365 + }, + }, + + "usage_excluding_cached_inference" : { + "total_cost": 0.0006090000000000001, + "gpt-35-turbo": { + "cost": 0.0006090000000000001, + "prompt_tokens": 242, + "completion_tokens": 123, + "total_tokens": 365 + }, } } ``` Note: - `actual_usage_summary` follows the same format. - If none of the agents incurred any cost (not having a client), then the total_usage_summary and actual_usage_summary will be `{'total_cost': 0}`. + If none of the agents incurred any cost (not having a client), then the usage_including_cached_inference and usage_excluding_cached_inference will be `{'total_cost': 0}`. """ def aggregate_summary(usage_summary: Dict[str, Any], agent_summary: Dict[str, Any]) -> None: @@ -69,15 +82,18 @@ def aggregate_summary(usage_summary: Dict[str, Any], agent_summary: Dict[str, An usage_summary[model]["completion_tokens"] += data.get("completion_tokens", 0) usage_summary[model]["total_tokens"] += data.get("total_tokens", 0) - total_usage_summary = {"total_cost": 0} - actual_usage_summary = {"total_cost": 0} + usage_including_cached_inference = {"total_cost": 0} + usage_excluding_cached_inference = {"total_cost": 0} for agent in agents: if getattr(agent, "client", None): - aggregate_summary(total_usage_summary, agent.client.total_usage_summary) - aggregate_summary(actual_usage_summary, agent.client.actual_usage_summary) + aggregate_summary(usage_including_cached_inference, agent.client.total_usage_summary) + aggregate_summary(usage_excluding_cached_inference, agent.client.actual_usage_summary) - return total_usage_summary, actual_usage_summary + return { + "usage_including_cached_inference": usage_including_cached_inference, + "usage_excluding_cached_inference": usage_excluding_cached_inference, + } def parse_tags_from_content(tag: str, content: Union[str, List[Dict[str, Any]]]) -> List[Dict[str, Dict[str, str]]]: diff --git a/autogen/browser_utils.py b/autogen/browser_utils.py index 41d2d62f825..99e51fcd4ca 100644 --- a/autogen/browser_utils.py +++ b/autogen/browser_utils.py @@ -1,14 +1,15 @@ +import io import json +import mimetypes import os -import requests import re -import markdownify -import io import uuid -import mimetypes +from typing import Any, Dict, List, Optional, Tuple, Union from urllib.parse import urljoin, urlparse + +import markdownify +import requests from bs4 import BeautifulSoup -from typing import Any, Dict, List, Optional, Union, Tuple # Optional PDF support IS_PDF_CAPABLE = False @@ -35,6 +36,7 @@ def __init__( start_page: Optional[str] = None, viewport_size: Optional[int] = 1024 * 8, downloads_folder: Optional[Union[str, None]] = None, + bing_base_url: str = "https://api.bing.microsoft.com/v7.0/search", bing_api_key: Optional[Union[str, None]] = None, request_kwargs: Optional[Union[Dict[str, Any], None]] = None, ): @@ -46,6 +48,7 @@ def __init__( self.viewport_current_page = 0 self.viewport_pages: List[Tuple[int, int]] = list() self.set_address(self.start_page) + self.bing_base_url = bing_base_url self.bing_api_key = bing_api_key self.request_kwargs = request_kwargs @@ -144,7 +147,7 @@ def _bing_api_call(self, query: str) -> Dict[str, Dict[str, List[Dict[str, Union request_kwargs["stream"] = False # Make the request - response = requests.get("https://api.bing.microsoft.com/v7.0/search", **request_kwargs) + response = requests.get(self.bing_base_url, **request_kwargs) response.raise_for_status() results = response.json() diff --git a/autogen/cache/__init__.py b/autogen/cache/__init__.py index febfa8c7c5d..ea547e20c8e 100644 --- a/autogen/cache/__init__.py +++ b/autogen/cache/__init__.py @@ -1,3 +1,4 @@ -from .cache import Cache, AbstractCache +from .abstract_cache_base import AbstractCache +from .cache import Cache __all__ = ["Cache", "AbstractCache"] diff --git a/autogen/cache/abstract_cache_base.py b/autogen/cache/abstract_cache_base.py index ebf1cecfa40..cfe501083fa 100644 --- a/autogen/cache/abstract_cache_base.py +++ b/autogen/cache/abstract_cache_base.py @@ -1,6 +1,6 @@ +import sys from types import TracebackType from typing import Any, Optional, Protocol, Type -import sys if sys.version_info >= (3, 11): from typing import Self diff --git a/autogen/cache/cache.py b/autogen/cache/cache.py index 31bbfa13529..6a15d993ff6 100644 --- a/autogen/cache/cache.py +++ b/autogen/cache/cache.py @@ -1,13 +1,12 @@ from __future__ import annotations + +import sys from types import TracebackType -from typing import Dict, Any, Optional, Type, Union +from typing import Any, Dict, Optional, Type, TypedDict, Union from .abstract_cache_base import AbstractCache - from .cache_factory import CacheFactory -import sys - if sys.version_info >= (3, 11): from typing import Self else: @@ -27,7 +26,12 @@ class Cache(AbstractCache): cache: The cache instance created based on the provided configuration. """ - ALLOWED_CONFIG_KEYS = ["cache_seed", "redis_url", "cache_path_root"] + ALLOWED_CONFIG_KEYS = [ + "cache_seed", + "redis_url", + "cache_path_root", + "cosmos_db_config", + ] @staticmethod def redis(cache_seed: Union[str, int] = 42, redis_url: str = "redis://localhost:6379/0") -> "Cache": @@ -57,6 +61,32 @@ def disk(cache_seed: Union[str, int] = 42, cache_path_root: str = ".cache") -> " """ return Cache({"cache_seed": cache_seed, "cache_path_root": cache_path_root}) + @staticmethod + def cosmos_db( + connection_string: Optional[str] = None, + container_id: Optional[str] = None, + cache_seed: Union[str, int] = 42, + client: Optional[any] = None, + ) -> "Cache": + """ + Create a Cosmos DB cache instance with 'autogen_cache' as database ID. + + Args: + connection_string (str, optional): Connection string to the Cosmos DB account. + container_id (str, optional): The container ID for the Cosmos DB account. + cache_seed (Union[str, int], optional): A seed for the cache. + client: Optional[CosmosClient]: Pass an existing Cosmos DB client. + Returns: + Cache: A Cache instance configured for Cosmos DB. + """ + cosmos_db_config = { + "connection_string": connection_string, + "database_id": "autogen_cache", + "container_id": container_id, + "client": client, + } + return Cache({"cache_seed": str(cache_seed), "cosmos_db_config": cosmos_db_config}) + def __init__(self, config: Dict[str, Any]): """ Initialize the Cache with the given configuration. @@ -70,15 +100,19 @@ def __init__(self, config: Dict[str, Any]): ValueError: If an invalid configuration key is provided. """ self.config = config + # Ensure that the seed is always treated as a string before being passed to any cache factory or stored. + self.config["cache_seed"] = str(self.config.get("cache_seed", 42)) + # validate config for key in self.config.keys(): if key not in self.ALLOWED_CONFIG_KEYS: raise ValueError(f"Invalid config key: {key}") # create cache instance self.cache = CacheFactory.cache_factory( - self.config.get("cache_seed", "42"), - self.config.get("redis_url", None), - self.config.get("cache_path_root", None), + seed=self.config["cache_seed"], + redis_url=self.config.get("redis_url"), + cache_path_root=self.config.get("cache_path_root"), + cosmosdb_config=self.config.get("cosmos_db_config"), ) def __enter__(self) -> "Cache": diff --git a/autogen/cache/cache_factory.py b/autogen/cache/cache_factory.py index e3c50e9eb2b..7c9d71884cb 100644 --- a/autogen/cache/cache_factory.py +++ b/autogen/cache/cache_factory.py @@ -1,32 +1,36 @@ -from typing import Optional, Union +import logging +import os +from typing import Any, Dict, Optional, Union + from .abstract_cache_base import AbstractCache from .disk_cache import DiskCache -import logging - class CacheFactory: @staticmethod def cache_factory( - seed: Union[str, int], redis_url: Optional[str] = None, cache_path_root: str = ".cache" + seed: Union[str, int], + redis_url: Optional[str] = None, + cache_path_root: str = ".cache", + cosmosdb_config: Optional[Dict[str, Any]] = None, ) -> AbstractCache: """ Factory function for creating cache instances. - Based on the provided redis_url, this function decides whether to create a RedisCache - or DiskCache instance. If RedisCache is available and redis_url is provided, - a RedisCache instance is created. Otherwise, a DiskCache instance is used. + This function decides whether to create a RedisCache, DiskCache, or CosmosDBCache instance + based on the provided parameters. If RedisCache is available and a redis_url is provided, + a RedisCache instance is created. If connection_string, database_id, and container_id + are provided, a CosmosDBCache is created. Otherwise, a DiskCache instance is used. Args: - seed (Union[str, int]): A string or int used as a seed or namespace for the cache. - This could be useful for creating distinct cache instances - or for namespacing keys in the cache. - redis_url (str or None): The URL for the Redis server. If this is None - or if RedisCache is not available, a DiskCache instance is created. + seed (Union[str, int]): Used as a seed or namespace for the cache. + redis_url (Optional[str]): URL for the Redis server. + cache_path_root (str): Root path for the disk cache. + cosmosdb_config (Optional[Dict[str, str]]): Dictionary containing 'connection_string', + 'database_id', and 'container_id' for Cosmos DB cache. Returns: - An instance of either RedisCache or DiskCache, depending on the availability of RedisCache - and the provided redis_url. + An instance of RedisCache, DiskCache, or CosmosDBCache. Examples: @@ -40,14 +44,36 @@ def cache_factory( ```python disk_cache = cache_factory("myseed", None) ``` + + Creating a Cosmos DB cache: + ```python + cosmos_cache = cache_factory("myseed", cosmosdb_config={ + "connection_string": "your_connection_string", + "database_id": "your_database_id", + "container_id": "your_container_id"} + ) + ``` + """ - if redis_url is not None: + if redis_url: try: from .redis_cache import RedisCache return RedisCache(seed, redis_url) except ImportError: - logging.warning("RedisCache is not available. Creating a DiskCache instance instead.") - return DiskCache(f"./{cache_path_root}/{seed}") - else: - return DiskCache(f"./{cache_path_root}/{seed}") + logging.warning( + "RedisCache is not available. Checking other cache options. The last fallback is DiskCache." + ) + + if cosmosdb_config: + try: + from .cosmos_db_cache import CosmosDBCache + + return CosmosDBCache.create_cache(seed, cosmosdb_config) + + except ImportError: + logging.warning("CosmosDBCache is not available. Fallback to DiskCache.") + + # Default to DiskCache if neither Redis nor Cosmos DB configurations are provided + path = os.path.join(cache_path_root, str(seed)) + return DiskCache(os.path.join(".", path)) diff --git a/autogen/cache/cosmos_db_cache.py b/autogen/cache/cosmos_db_cache.py new file mode 100644 index 00000000000..b85be923c2f --- /dev/null +++ b/autogen/cache/cosmos_db_cache.py @@ -0,0 +1,144 @@ +# Install Azure Cosmos DB SDK if not already + +import pickle +from typing import Any, Optional, TypedDict, Union + +from azure.cosmos import CosmosClient, PartitionKey, exceptions +from azure.cosmos.exceptions import CosmosResourceNotFoundError + +from autogen.cache.abstract_cache_base import AbstractCache + + +class CosmosDBConfig(TypedDict, total=False): + connection_string: str + database_id: str + container_id: str + cache_seed: Optional[Union[str, int]] + client: Optional[CosmosClient] + + +class CosmosDBCache(AbstractCache): + """ + Synchronous implementation of AbstractCache using Azure Cosmos DB NoSQL API. + + This class provides a concrete implementation of the AbstractCache + interface using Azure Cosmos DB for caching data, with synchronous operations. + + Attributes: + seed (Union[str, int]): A seed or namespace used as a partition key. + client (CosmosClient): The Cosmos DB client used for caching. + container: The container instance used for caching. + """ + + def __init__(self, seed: Union[str, int], cosmosdb_config: CosmosDBConfig): + """ + Initialize the CosmosDBCache instance. + + Args: + seed (Union[str, int]): A seed or namespace for the cache, used as a partition key. + connection_string (str): The connection string for the Cosmos DB account. + container_id (str): The container ID to be used for caching. + client (Optional[CosmosClient]): An existing CosmosClient instance to be used for caching. + """ + self.seed = str(seed) + self.client = cosmosdb_config.get("client") or CosmosClient.from_connection_string( + cosmosdb_config["connection_string"] + ) + database_id = cosmosdb_config.get("database_id", "autogen_cache") + self.database = self.client.get_database_client(database_id) + container_id = cosmosdb_config.get("container_id") + self.container = self.database.create_container_if_not_exists( + id=container_id, partition_key=PartitionKey(path="/partitionKey") + ) + + @classmethod + def create_cache(cls, seed: Union[str, int], cosmosdb_config: CosmosDBConfig): + """ + Factory method to create a CosmosDBCache instance based on the provided configuration. + This method decides whether to use an existing CosmosClient or create a new one. + """ + if "client" in cosmosdb_config and isinstance(cosmosdb_config["client"], CosmosClient): + return cls.from_existing_client(seed, **cosmosdb_config) + else: + return cls.from_config(seed, cosmosdb_config) + + @classmethod + def from_config(cls, seed: Union[str, int], cosmosdb_config: CosmosDBConfig): + return cls(str(seed), cosmosdb_config) + + @classmethod + def from_connection_string(cls, seed: Union[str, int], connection_string: str, database_id: str, container_id: str): + config = {"connection_string": connection_string, "database_id": database_id, "container_id": container_id} + return cls(str(seed), config) + + @classmethod + def from_existing_client(cls, seed: Union[str, int], client: CosmosClient, database_id: str, container_id: str): + config = {"client": client, "database_id": database_id, "container_id": container_id} + return cls(str(seed), config) + + def get(self, key: str, default: Optional[Any] = None) -> Optional[Any]: + """ + Retrieve an item from the Cosmos DB cache. + + Args: + key (str): The key identifying the item in the cache. + default (optional): The default value to return if the key is not found. + + Returns: + The deserialized value associated with the key if found, else the default value. + """ + try: + response = self.container.read_item(item=key, partition_key=str(self.seed)) + return pickle.loads(response["data"]) + except CosmosResourceNotFoundError: + return default + except Exception as e: + # Log the exception or rethrow after logging if needed + # Consider logging or handling the error appropriately here + raise e + + def set(self, key: str, value: Any) -> None: + """ + Set an item in the Cosmos DB cache. + + Args: + key (str): The key under which the item is to be stored. + value: The value to be stored in the cache. + + Notes: + The value is serialized using pickle before being stored. + """ + try: + serialized_value = pickle.dumps(value) + item = {"id": key, "partitionKey": str(self.seed), "data": serialized_value} + self.container.upsert_item(item) + except Exception as e: + # Log or handle exception + raise e + + def close(self) -> None: + """ + Close the Cosmos DB client. + + Perform any necessary cleanup, such as closing network connections. + """ + # CosmosClient doesn"t require explicit close in the current SDK + # If you created the client inside this class, you should close it if necessary + pass + + def __enter__(self): + """ + Context management entry. + + Returns: + self: The instance itself. + """ + return self + + def __exit__(self, exc_type: Optional[type], exc_value: Optional[Exception], traceback: Optional[Any]) -> None: + """ + Context management exit. + + Perform cleanup actions such as closing the Cosmos DB client. + """ + self.close() diff --git a/autogen/cache/disk_cache.py b/autogen/cache/disk_cache.py index 2cca53e6d2f..7c68e7e908c 100644 --- a/autogen/cache/disk_cache.py +++ b/autogen/cache/disk_cache.py @@ -1,8 +1,10 @@ +import sys from types import TracebackType from typing import Any, Optional, Type, Union + import diskcache + from .abstract_cache_base import AbstractCache -import sys if sys.version_info >= (3, 11): from typing import Self diff --git a/autogen/cache/in_memory_cache.py b/autogen/cache/in_memory_cache.py new file mode 100644 index 00000000000..b79f9ecfa4f --- /dev/null +++ b/autogen/cache/in_memory_cache.py @@ -0,0 +1,55 @@ +import sys +from types import TracebackType +from typing import Any, Dict, Optional, Type, Union + +from .abstract_cache_base import AbstractCache + +if sys.version_info >= (3, 11): + from typing import Self +else: + from typing_extensions import Self + + +class InMemoryCache(AbstractCache): + + def __init__(self, seed: Union[str, int] = ""): + self._seed = str(seed) + self._cache: Dict[str, Any] = {} + + def _prefixed_key(self, key: str) -> str: + separator = "_" if self._seed else "" + return f"{self._seed}{separator}{key}" + + def get(self, key: str, default: Optional[Any] = None) -> Optional[Any]: + result = self._cache.get(self._prefixed_key(key)) + if result is None: + return default + return result + + def set(self, key: str, value: Any) -> None: + self._cache[self._prefixed_key(key)] = value + + def close(self) -> None: + pass + + def __enter__(self) -> Self: + """ + Enter the runtime context related to the object. + + Returns: + self: The instance itself. + """ + return self + + def __exit__( + self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType] + ) -> None: + """ + Exit the runtime context related to the object. + + Args: + exc_type: The exception type if an exception was raised in the context. + exc_value: The exception value if an exception was raised in the context. + traceback: The traceback if an exception was raised in the context. + """ + self.close() diff --git a/autogen/cache/redis_cache.py b/autogen/cache/redis_cache.py index d125d3ba203..36d601af702 100644 --- a/autogen/cache/redis_cache.py +++ b/autogen/cache/redis_cache.py @@ -1,8 +1,10 @@ import pickle +import sys from types import TracebackType from typing import Any, Optional, Type, Union + import redis -import sys + from .abstract_cache_base import AbstractCache if sys.version_info >= (3, 11): diff --git a/autogen/code_utils.py b/autogen/code_utils.py index 57a817855f7..98ed6067066 100644 --- a/autogen/code_utils.py +++ b/autogen/code_utils.py @@ -6,14 +6,16 @@ import subprocess import sys import time +import venv from concurrent.futures import ThreadPoolExecutor, TimeoutError from hashlib import md5 +from types import SimpleNamespace from typing import Any, Callable, Dict, List, Optional, Tuple, Union -from autogen import oai - import docker +from autogen import oai + from .types import UserMessageImageContentPart, UserMessageTextContentPart SENTINEL = object() @@ -35,12 +37,13 @@ DEFAULT_TIMEOUT = 600 WIN32 = sys.platform == "win32" PATH_SEPARATOR = WIN32 and "\\" or "/" +PYTHON_VARIANTS = ["python", "Python", "py"] logger = logging.getLogger(__name__) def content_str(content: Union[str, List[Union[UserMessageTextContentPart, UserMessageImageContentPart]], None]) -> str: - """Converts the `content` field of an OpenAI merssage into a string format. + """Converts the `content` field of an OpenAI message into a string format. This function processes content that may be a string, a list of mixed text and image URLs, or None, and converts it into a string. Text is directly appended to the result string, while image URLs are @@ -244,10 +247,14 @@ def get_powershell_command(): def _cmd(lang: str) -> str: + if lang in PYTHON_VARIANTS: + return "python" if lang.startswith("python") or lang in ["bash", "sh"]: return lang if lang in ["shell"]: return "sh" + if lang == "javascript": + return "node" if lang in ["ps1", "pwsh", "powershell"]: powershell_command = get_powershell_command() return powershell_command @@ -278,7 +285,7 @@ def in_docker_container() -> bool: return os.path.exists("/.dockerenv") -def decide_use_docker(use_docker) -> bool: +def decide_use_docker(use_docker: Optional[bool]) -> Optional[bool]: if use_docker is None: env_var_use_docker = os.environ.get("AUTOGEN_USE_DOCKER", "True") @@ -714,3 +721,19 @@ def implement( # cost += metrics["gen_cost"] # if metrics["succeed_assertions"] or i == len(configs) - 1: # return responses[metrics["index_selected"]], cost, i + + +def create_virtual_env(dir_path: str, **env_args) -> SimpleNamespace: + """Creates a python virtual environment and returns the context. + + Args: + dir_path (str): Directory path where the env will be created. + **env_args: Any extra args to pass to the `EnvBuilder` + + Returns: + SimpleNamespace: the virtual env context object.""" + if not env_args: + env_args = {"with_pip": True} + env_builder = venv.EnvBuilder(**env_args) + env_builder.create(dir_path) + return env_builder.ensure_directories(dir_path) diff --git a/autogen/coding/__init__.py b/autogen/coding/__init__.py index 2ba4e9b0734..2f53b88ca3d 100644 --- a/autogen/coding/__init__.py +++ b/autogen/coding/__init__.py @@ -1,8 +1,8 @@ from .base import CodeBlock, CodeExecutor, CodeExtractor, CodeResult +from .docker_commandline_code_executor import DockerCommandLineCodeExecutor from .factory import CodeExecutorFactory -from .markdown_code_extractor import MarkdownCodeExtractor from .local_commandline_code_executor import LocalCommandLineCodeExecutor -from .docker_commandline_code_executor import DockerCommandLineCodeExecutor +from .markdown_code_extractor import MarkdownCodeExtractor __all__ = ( "CodeBlock", diff --git a/autogen/coding/base.py b/autogen/coding/base.py index f60ff0de85e..ccbfe6b9293 100644 --- a/autogen/coding/base.py +++ b/autogen/coding/base.py @@ -1,4 +1,5 @@ from __future__ import annotations + from typing import Any, List, Literal, Mapping, Optional, Protocol, TypedDict, Union, runtime_checkable from pydantic import BaseModel, Field diff --git a/autogen/coding/docker_commandline_code_executor.py b/autogen/coding/docker_commandline_code_executor.py index f1db7cd07e7..6d8f4e309c8 100644 --- a/autogen/coding/docker_commandline_code_executor.py +++ b/autogen/coding/docker_commandline_code_executor.py @@ -1,22 +1,22 @@ from __future__ import annotations + import atexit -from hashlib import md5 import logging +import sys +import uuid +from hashlib import md5 from pathlib import Path from time import sleep from types import TracebackType -import uuid -from typing import Any, List, Optional, Type, Union +from typing import Any, ClassVar, Dict, List, Optional, Type, Union + import docker from docker.errors import ImageNotFound -from .utils import _get_file_name_from_content, silence_pip -from .base import CommandLineCodeResult - from ..code_utils import TIMEOUT_MSG, _cmd -from .base import CodeBlock, CodeExecutor, CodeExtractor +from .base import CodeBlock, CodeExecutor, CodeExtractor, CommandLineCodeResult from .markdown_code_extractor import MarkdownCodeExtractor -import sys +from .utils import _get_file_name_from_content, silence_pip if sys.version_info >= (3, 11): from typing import Self @@ -39,14 +39,30 @@ def _wait_for_ready(container: Any, timeout: int = 60, stop_time: float = 0.1) - class DockerCommandLineCodeExecutor(CodeExecutor): + DEFAULT_EXECUTION_POLICY: ClassVar[Dict[str, bool]] = { + "bash": True, + "shell": True, + "sh": True, + "pwsh": True, + "powershell": True, + "ps1": True, + "python": True, + "javascript": False, + "html": False, + "css": False, + } + LANGUAGE_ALIASES: ClassVar[Dict[str, str]] = {"py": "python", "js": "javascript"} + def __init__( self, image: str = "python:3-slim", container_name: Optional[str] = None, timeout: int = 60, work_dir: Union[Path, str] = Path("."), + bind_dir: Optional[Union[Path, str]] = None, auto_remove: bool = True, stop_container: bool = True, + execution_policies: Optional[Dict[str, bool]] = None, ): """(Experimental) A code executor class that executes code through a command line environment in a Docker container. @@ -67,6 +83,9 @@ def __init__( timeout (int, optional): The timeout for code execution. Defaults to 60. work_dir (Union[Path, str], optional): The working directory for the code execution. Defaults to Path("."). + bind_dir (Union[Path, str], optional): The directory that will be bound + to the code executor container. Useful for cases where you want to spawn + the container from within a container. Defaults to work_dir. auto_remove (bool, optional): If true, will automatically remove the Docker container when it is stopped. Defaults to True. stop_container (bool, optional): If true, will automatically stop the @@ -76,18 +95,19 @@ def __init__( Raises: ValueError: On argument error, or if the container fails to start. """ - if timeout < 1: raise ValueError("Timeout must be greater than or equal to 1.") if isinstance(work_dir, str): work_dir = Path(work_dir) + work_dir.mkdir(exist_ok=True) - if not work_dir.exists(): - raise ValueError(f"Working directory {work_dir} does not exist.") + if bind_dir is None: + bind_dir = work_dir + elif isinstance(bind_dir, str): + bind_dir = Path(bind_dir) client = docker.from_env() - # Check if the image exists try: client.images.get(image) @@ -106,7 +126,7 @@ def __init__( entrypoint="/bin/sh", tty=True, auto_remove=auto_remove, - volumes={str(work_dir.resolve()): {"bind": "/workspace", "mode": "rw"}}, + volumes={str(bind_dir.resolve()): {"bind": "/workspace", "mode": "rw"}}, working_dir="/workspace", ) self._container.start() @@ -119,7 +139,6 @@ def cleanup() -> None: container.stop() except docker.errors.NotFound: pass - atexit.unregister(cleanup) if stop_container: @@ -133,6 +152,10 @@ def cleanup() -> None: self._timeout = timeout self._work_dir: Path = work_dir + self._bind_dir: Path = bind_dir + self.execution_policies = self.DEFAULT_EXECUTION_POLICY.copy() + if execution_policies is not None: + self.execution_policies.update(execution_policies) @property def timeout(self) -> int: @@ -144,6 +167,11 @@ def work_dir(self) -> Path: """(Experimental) The working directory for the code execution.""" return self._work_dir + @property + def bind_dir(self) -> Path: + """(Experimental) The binding directory for the code execution container.""" + return self._bind_dir + @property def code_extractor(self) -> CodeExtractor: """(Experimental) Export a code extractor that can be used by an agent.""" @@ -165,35 +193,42 @@ def execute_code_blocks(self, code_blocks: List[CodeBlock]) -> CommandLineCodeRe files = [] last_exit_code = 0 for code_block in code_blocks: - lang = code_block.language + lang = self.LANGUAGE_ALIASES.get(code_block.language.lower(), code_block.language.lower()) + if lang not in self.DEFAULT_EXECUTION_POLICY: + outputs.append(f"Unsupported language {lang}\n") + last_exit_code = 1 + break + + execute_code = self.execution_policies.get(lang, False) code = silence_pip(code_block.code, lang) + # Check if there is a filename comment try: - # Check if there is a filename comment - filename = _get_file_name_from_content(code, Path("/workspace")) + filename = _get_file_name_from_content(code, self._work_dir) except ValueError: - return CommandLineCodeResult(exit_code=1, output="Filename is not in the workspace") + outputs.append("Filename is not in the workspace") + last_exit_code = 1 + break - if filename is None: - # create a file with an automatically generated name - code_hash = md5(code.encode()).hexdigest() - filename = f"tmp_code_{code_hash}.{'py' if lang.startswith('python') else lang}" + if not filename: + filename = f"tmp_code_{md5(code.encode()).hexdigest()}.{lang}" code_path = self._work_dir / filename with code_path.open("w", encoding="utf-8") as fout: fout.write(code) + files.append(code_path) - command = ["timeout", str(self._timeout), _cmd(lang), filename] + if not execute_code: + outputs.append(f"Code saved to {str(code_path)}\n") + continue + command = ["timeout", str(self._timeout), _cmd(lang), filename] result = self._container.exec_run(command) exit_code = result.exit_code output = result.output.decode("utf-8") if exit_code == 124: - output += "\n" - output += TIMEOUT_MSG - + output += "\n" + TIMEOUT_MSG outputs.append(output) - files.append(code_path) last_exit_code = exit_code if exit_code != 0: diff --git a/autogen/coding/factory.py b/autogen/coding/factory.py index 0c2d41b89da..b484d99cda1 100644 --- a/autogen/coding/factory.py +++ b/autogen/coding/factory.py @@ -1,4 +1,4 @@ -from .base import CodeExecutor, CodeExecutionConfig +from .base import CodeExecutionConfig, CodeExecutor __all__ = ("CodeExecutorFactory",) diff --git a/autogen/coding/func_with_reqs.py b/autogen/coding/func_with_reqs.py index c37c12c1e2f..6f199573822 100644 --- a/autogen/coding/func_with_reqs.py +++ b/autogen/coding/func_with_reqs.py @@ -1,16 +1,23 @@ from __future__ import annotations -import inspect + import functools -from typing import Any, Callable, List, TypeVar, Generic, Union -from typing_extensions import ParamSpec -from textwrap import indent, dedent +import importlib +import inspect from dataclasses import dataclass, field +from importlib.abc import SourceLoader +from textwrap import dedent, indent +from typing import Any, Callable, Generic, List, TypeVar, Union + +from typing_extensions import ParamSpec T = TypeVar("T") P = ParamSpec("P") -def _to_code(func: Union[FunctionWithRequirements[T, P], Callable[P, T]]) -> str: +def _to_code(func: Union[FunctionWithRequirements[T, P], Callable[P, T], FunctionWithRequirementsStr]) -> str: + if isinstance(func, FunctionWithRequirementsStr): + return func.func + code = inspect.getsource(func) # Strip the decorator if code.startswith("@"): @@ -50,6 +57,57 @@ def to_str(i: Union[str, Alias]) -> str: return f"from {im.module} import {imports}" +class _StringLoader(SourceLoader): + def __init__(self, data: str): + self.data = data + + def get_source(self, fullname: str) -> str: + return self.data + + def get_data(self, path: str) -> bytes: + return self.data.encode("utf-8") + + def get_filename(self, fullname: str) -> str: + return "/" + fullname + ".py" + + +@dataclass +class FunctionWithRequirementsStr: + func: str + _compiled_func: Callable[..., Any] + _func_name: str + python_packages: List[str] = field(default_factory=list) + global_imports: List[Import] = field(default_factory=list) + + def __init__(self, func: str, python_packages: List[str] = [], global_imports: List[Import] = []): + self.func = func + self.python_packages = python_packages + self.global_imports = global_imports + + module_name = "func_module" + loader = _StringLoader(func) + spec = importlib.util.spec_from_loader(module_name, loader) + if spec is None: + raise ValueError("Could not create spec") + module = importlib.util.module_from_spec(spec) + if spec.loader is None: + raise ValueError("Could not create loader") + + try: + spec.loader.exec_module(module) + except Exception as e: + raise ValueError(f"Could not compile function: {e}") from e + + functions = inspect.getmembers(module, inspect.isfunction) + if len(functions) != 1: + raise ValueError("The string must contain exactly one function") + + self._func_name, self._compiled_func = functions[0] + + def __call__(self, *args: Any, **kwargs: Any) -> None: + raise NotImplementedError("String based function with requirement objects are not directly callable") + + @dataclass class FunctionWithRequirements(Generic[T, P]): func: Callable[P, T] @@ -62,6 +120,12 @@ def from_callable( ) -> FunctionWithRequirements[T, P]: return cls(python_packages=python_packages, global_imports=global_imports, func=func) + @staticmethod + def from_str( + func: str, python_packages: List[str] = [], global_imports: List[Import] = [] + ) -> FunctionWithRequirementsStr: + return FunctionWithRequirementsStr(func=func, python_packages=python_packages, global_imports=global_imports) + # Type this based on F def __call__(self, *args: P.args, **kwargs: P.kwargs) -> T: return self.func(*args, **kwargs) @@ -91,11 +155,13 @@ def wrapper(func: Callable[P, T]) -> FunctionWithRequirements[T, P]: return wrapper -def _build_python_functions_file(funcs: List[Union[FunctionWithRequirements[Any, P], Callable[..., Any]]]) -> str: +def _build_python_functions_file( + funcs: List[Union[FunctionWithRequirements[Any, P], Callable[..., Any], FunctionWithRequirementsStr]] +) -> str: # First collect all global imports global_imports = set() for func in funcs: - if isinstance(func, FunctionWithRequirements): + if isinstance(func, (FunctionWithRequirements, FunctionWithRequirementsStr)): global_imports.update(func.global_imports) content = "\n".join(map(_import_to_str, global_imports)) + "\n\n" @@ -106,7 +172,7 @@ def _build_python_functions_file(funcs: List[Union[FunctionWithRequirements[Any, return content -def to_stub(func: Callable[..., Any]) -> str: +def to_stub(func: Union[Callable[..., Any], FunctionWithRequirementsStr]) -> str: """Generate a stub for a function as a string Args: @@ -115,6 +181,9 @@ def to_stub(func: Callable[..., Any]) -> str: Returns: str: The stub for the function """ + if isinstance(func, FunctionWithRequirementsStr): + return to_stub(func._compiled_func) + content = f"def {func.__name__}{inspect.signature(func)}:\n" docstring = func.__doc__ diff --git a/autogen/coding/jupyter/__init__.py b/autogen/coding/jupyter/__init__.py index 5c1a9607f56..f6f02313ec1 100644 --- a/autogen/coding/jupyter/__init__.py +++ b/autogen/coding/jupyter/__init__.py @@ -1,9 +1,9 @@ from .base import JupyterConnectable, JupyterConnectionInfo -from .jupyter_client import JupyterClient -from .local_jupyter_server import LocalJupyterServer from .docker_jupyter_server import DockerJupyterServer from .embedded_ipython_code_executor import EmbeddedIPythonCodeExecutor +from .jupyter_client import JupyterClient from .jupyter_code_executor import JupyterCodeExecutor +from .local_jupyter_server import LocalJupyterServer __all__ = [ "JupyterConnectable", diff --git a/autogen/coding/jupyter/base.py b/autogen/coding/jupyter/base.py index d896b6ac3cc..0e7acaf1e87 100644 --- a/autogen/coding/jupyter/base.py +++ b/autogen/coding/jupyter/base.py @@ -10,9 +10,9 @@ class JupyterConnectionInfo: """`str` - Host of the Jupyter gateway server""" use_https: bool """`bool` - Whether to use HTTPS""" - port: int - """`int` - Port of the Jupyter gateway server""" - token: Optional[str] + port: Optional[int] = None + """`Optional[int]` - Port of the Jupyter gateway server. If None, the default port is used""" + token: Optional[str] = None """`Optional[str]` - Token for authentication. If None, no token is used""" diff --git a/autogen/coding/jupyter/docker_jupyter_server.py b/autogen/coding/jupyter/docker_jupyter_server.py index 3b9462186b9..83455e27238 100644 --- a/autogen/coding/jupyter/docker_jupyter_server.py +++ b/autogen/coding/jupyter/docker_jupyter_server.py @@ -1,15 +1,16 @@ from __future__ import annotations -from pathlib import Path +import atexit +import io +import logging +import secrets import sys -from types import TracebackType import uuid +from pathlib import Path +from types import TracebackType from typing import Dict, Optional, Type, Union + import docker -import secrets -import io -import atexit -import logging from ..docker_commandline_code_executor import _wait_for_ready @@ -19,8 +20,8 @@ from typing_extensions import Self -from .jupyter_client import JupyterClient from .base import JupyterConnectable, JupyterConnectionInfo +from .jupyter_client import JupyterClient class DockerJupyterServer(JupyterConnectable): diff --git a/autogen/coding/jupyter/embedded_ipython_code_executor.py b/autogen/coding/jupyter/embedded_ipython_code_executor.py index 0d647082a3c..f9200c7a580 100644 --- a/autogen/coding/jupyter/embedded_ipython_code_executor.py +++ b/autogen/coding/jupyter/embedded_ipython_code_executor.py @@ -1,9 +1,9 @@ import base64 import json import os -from pathlib import Path import re import uuid +from pathlib import Path from queue import Empty from typing import Any, ClassVar, List diff --git a/autogen/coding/jupyter/jupyter_client.py b/autogen/coding/jupyter/jupyter_client.py index 8f97ab82418..b3de374fce9 100644 --- a/autogen/coding/jupyter/jupyter_client.py +++ b/autogen/coding/jupyter/jupyter_client.py @@ -1,22 +1,22 @@ from __future__ import annotations +import sys from dataclasses import dataclass from types import TracebackType from typing import Any, Dict, List, Optional, Type, cast -import sys if sys.version_info >= (3, 11): from typing import Self else: from typing_extensions import Self +import datetime import json import uuid -import datetime -import requests -from requests.adapters import HTTPAdapter, Retry +import requests import websocket +from requests.adapters import HTTPAdapter, Retry from websocket import WebSocket from .base import JupyterConnectionInfo @@ -41,10 +41,12 @@ def _get_headers(self) -> Dict[str, str]: def _get_api_base_url(self) -> str: protocol = "https" if self._connection_info.use_https else "http" - return f"{protocol}://{self._connection_info.host}:{self._connection_info.port}" + port = f":{self._connection_info.port}" if self._connection_info.port else "" + return f"{protocol}://{self._connection_info.host}{port}" def _get_ws_base_url(self) -> str: - return f"ws://{self._connection_info.host}:{self._connection_info.port}" + port = f":{self._connection_info.port}" if self._connection_info.port else "" + return f"ws://{self._connection_info.host}{port}" def list_kernel_specs(self) -> Dict[str, Dict[str, str]]: response = self._session.get(f"{self._get_api_base_url()}/api/kernelspecs", headers=self._get_headers()) diff --git a/autogen/coding/jupyter/jupyter_code_executor.py b/autogen/coding/jupyter/jupyter_code_executor.py index 4d926773517..833565ab47c 100644 --- a/autogen/coding/jupyter/jupyter_code_executor.py +++ b/autogen/coding/jupyter/jupyter_code_executor.py @@ -1,12 +1,12 @@ import base64 import json import os -from pathlib import Path import re -from types import TracebackType +import sys import uuid +from pathlib import Path +from types import TracebackType from typing import Any, ClassVar, List, Optional, Type, Union -import sys from autogen.coding.utils import silence_pip diff --git a/autogen/coding/jupyter/local_jupyter_server.py b/autogen/coding/jupyter/local_jupyter_server.py index 0709f55ee4e..9b892303848 100644 --- a/autogen/coding/jupyter/local_jupyter_server.py +++ b/autogen/coding/jupyter/local_jupyter_server.py @@ -1,14 +1,14 @@ from __future__ import annotations -from types import TracebackType -from typing import Optional, Type, Union, cast -import subprocess -import signal -import sys +import atexit import json import secrets +import signal import socket -import atexit +import subprocess +import sys +from types import TracebackType +from typing import Optional, Type, Union, cast if sys.version_info >= (3, 11): from typing import Self diff --git a/autogen/coding/local_commandline_code_executor.py b/autogen/coding/local_commandline_code_executor.py index b75f54ff121..29172bbe922 100644 --- a/autogen/coding/local_commandline_code_executor.py +++ b/autogen/coding/local_commandline_code_executor.py @@ -1,32 +1,60 @@ -from hashlib import md5 -from pathlib import Path +import logging +import os import re -from string import Template +import subprocess import sys import warnings -from typing import Any, Callable, ClassVar, List, TypeVar, Union, cast +from hashlib import md5 +from pathlib import Path +from string import Template +from types import SimpleNamespace +from typing import Any, Callable, ClassVar, Dict, List, Optional, Union + from typing_extensions import ParamSpec -from autogen.coding.func_with_reqs import FunctionWithRequirements, _build_python_functions_file, to_stub -from ..code_utils import TIMEOUT_MSG, WIN32, _cmd +from autogen.coding.func_with_reqs import ( + FunctionWithRequirements, + FunctionWithRequirementsStr, + _build_python_functions_file, + to_stub, +) + +from ..code_utils import PYTHON_VARIANTS, TIMEOUT_MSG, WIN32, _cmd from .base import CodeBlock, CodeExecutor, CodeExtractor, CommandLineCodeResult from .markdown_code_extractor import MarkdownCodeExtractor - from .utils import _get_file_name_from_content, silence_pip -import subprocess - -import logging - __all__ = ("LocalCommandLineCodeExecutor",) A = ParamSpec("A") class LocalCommandLineCodeExecutor(CodeExecutor): - SUPPORTED_LANGUAGES: ClassVar[List[str]] = ["bash", "shell", "sh", "pwsh", "powershell", "ps1", "python"] - FUNCTIONS_MODULE: ClassVar[str] = "functions" - FUNCTIONS_FILENAME: ClassVar[str] = "functions.py" + SUPPORTED_LANGUAGES: ClassVar[List[str]] = [ + "bash", + "shell", + "sh", + "pwsh", + "powershell", + "ps1", + "python", + "javascript", + "html", + "css", + ] + DEFAULT_EXECUTION_POLICY: ClassVar[Dict[str, bool]] = { + "bash": True, + "shell": True, + "sh": True, + "pwsh": True, + "powershell": True, + "ps1": True, + "python": True, + "javascript": False, + "html": False, + "css": False, + } + FUNCTION_PROMPT_TEMPLATE: ClassVar[ str ] = """You have access to the following user defined functions. They can be accessed from the module called `$module_name` by their function names. @@ -38,31 +66,45 @@ class LocalCommandLineCodeExecutor(CodeExecutor): def __init__( self, timeout: int = 60, + virtual_env_context: Optional[SimpleNamespace] = None, work_dir: Union[Path, str] = Path("."), - functions: List[Union[FunctionWithRequirements[Any, A], Callable[..., Any]]] = [], + functions: List[Union[FunctionWithRequirements[Any, A], Callable[..., Any], FunctionWithRequirementsStr]] = [], + functions_module: str = "functions", + execution_policies: Optional[Dict[str, bool]] = None, ): - """(Experimental) A code executor class that executes code through a local command line + """(Experimental) A code executor class that executes or saves LLM generated code a local command line environment. - **This will execute LLM generated code on the local machine.** + **This will execute or save LLM generated code on the local machine.** + + Each code block is saved as a file in the working directory. Depending on the execution policy, + the code may be executed in a separate process. + The code blocks are executed or save in the order they are received. + Command line code is sanitized against a list of dangerous commands to prevent self-destructive commands from being executed, + which could potentially affect the user's environment. Supported languages include Python, shell scripts (bash, shell, sh), + PowerShell (pwsh, powershell, ps1), HTML, CSS, and JavaScript. + Execution policies determine whether each language's code blocks are executed or saved only. + + ## Execution with a Python virtual environment + A python virtual env can be used to execute code and install dependencies. This has the added benefit of not polluting the + base environment with unwanted modules. + ```python + from autogen.code_utils import create_virtual_env + from autogen.coding import LocalCommandLineCodeExecutor - Each code block is saved as a file and executed in a separate process in - the working directory, and a unique file is generated and saved in the - working directory for each code block. - The code blocks are executed in the order they are received. - Command line code is sanitized using regular expression match against a list of dangerous commands in order to prevent self-destructive - commands from being executed which may potentially affect the users environment. - Currently the only supported languages is Python and shell scripts. - For Python code, use the language "python" for the code block. - For shell scripts, use the language "bash", "shell", or "sh" for the code - block. + venv_dir = ".venv" + venv_context = create_virtual_env(venv_dir) + + executor = LocalCommandLineCodeExecutor(virtual_env_context=venv_context) + ``` Args: - timeout (int): The timeout for code execution. Default is 60. - work_dir (str): The working directory for the code execution. If None, - a default working directory will be used. The default working - directory is the current directory ".". - functions (List[Union[FunctionWithRequirements[Any, A], Callable[..., Any]]]): A list of functions that are available to the code executor. Default is an empty list. + timeout (int): The timeout for code execution, default is 60 seconds. + virtual_env_context (Optional[SimpleNamespace]): The virtual environment context to use. + work_dir (Union[Path, str]): The working directory for code execution, defaults to the current directory. + functions (List[Union[FunctionWithRequirements[Any, A], Callable[..., Any], FunctionWithRequirementsStr]]): A list of callable functions available to the executor. + functions_module (str): The module name under which functions are accessible. + execution_policies (Optional[Dict[str, bool]]): A dictionary mapping languages to execution policies (True for execution, False for saving only). Defaults to class-wide DEFAULT_EXECUTION_POLICY. """ if timeout < 1: @@ -71,11 +113,16 @@ def __init__( if isinstance(work_dir, str): work_dir = Path(work_dir) - if not work_dir.exists(): - raise ValueError(f"Working directory {work_dir} does not exist.") + if not functions_module.isidentifier(): + raise ValueError("Module name must be a valid Python identifier") + + self._functions_module = functions_module + + work_dir.mkdir(exist_ok=True) self._timeout = timeout self._work_dir: Path = work_dir + self._virtual_env_context: Optional[SimpleNamespace] = virtual_env_context self._functions = functions # Setup could take some time so we intentionally wait for the first code block to do it. @@ -84,6 +131,10 @@ def __init__( else: self._setup_functions_complete = True + self.execution_policies = self.DEFAULT_EXECUTION_POLICY.copy() + if execution_policies is not None: + self.execution_policies.update(execution_policies) + def format_functions_for_prompt(self, prompt_template: str = FUNCTION_PROMPT_TEMPLATE) -> str: """(Experimental) Format the functions for a prompt. @@ -97,15 +148,21 @@ def format_functions_for_prompt(self, prompt_template: str = FUNCTION_PROMPT_TEM Returns: str: The formatted prompt. """ - template = Template(prompt_template) return template.substitute( - module_name=self.FUNCTIONS_MODULE, + module_name=self._functions_module, functions="\n\n".join([to_stub(func) for func in self._functions]), ) @property - def functions(self) -> List[Union[FunctionWithRequirements[Any, A], Callable[..., Any]]]: + def functions_module(self) -> str: + """(Experimental) The module name for the functions.""" + return self._functions_module + + @property + def functions( + self, + ) -> List[Union[FunctionWithRequirements[Any, A], Callable[..., Any], FunctionWithRequirementsStr]]: """(Experimental) The functions that are available to the code executor.""" return self._functions @@ -148,7 +205,7 @@ def sanitize_command(lang: str, code: str) -> None: def _setup_functions(self) -> None: func_file_content = _build_python_functions_file(self._functions) - func_file = self._work_dir / self.FUNCTIONS_FILENAME + func_file = self._work_dir / f"{self._functions_module}.py" func_file.write_text(func_file_content) # Collect requirements @@ -157,26 +214,23 @@ def _setup_functions(self) -> None: required_packages = list(set(flattened_packages)) if len(required_packages) > 0: logging.info("Ensuring packages are installed in executor.") - - cmd = [sys.executable, "-m", "pip", "install"] - cmd.extend(required_packages) - + if self._virtual_env_context: + py_executable = self._virtual_env_context.env_exe + else: + py_executable = sys.executable + cmd = [py_executable, "-m", "pip", "install"] + required_packages try: result = subprocess.run( cmd, cwd=self._work_dir, capture_output=True, text=True, timeout=float(self._timeout) ) except subprocess.TimeoutExpired as e: raise ValueError("Pip install timed out") from e - if result.returncode != 0: raise ValueError(f"Pip install failed. {result.stdout}, {result.stderr}") - # Attempt to load the function file to check for syntax errors, imports etc. exec_result = self._execute_code_dont_check_setup([CodeBlock(code=func_file_content, language="python")]) - if exec_result.exit_code != 0: raise ValueError(f"Functions failed to load: {exec_result.output}") - self._setup_functions_complete = True def execute_code_blocks(self, code_blocks: List[CodeBlock]) -> CommandLineCodeResult: @@ -187,10 +241,8 @@ def execute_code_blocks(self, code_blocks: List[CodeBlock]) -> CommandLineCodeRe Returns: CommandLineCodeResult: The result of the code execution.""" - if not self._setup_functions_complete: self._setup_functions() - return self._execute_code_dont_check_setup(code_blocks) def _execute_code_dont_check_setup(self, code_blocks: List[CodeBlock]) -> CommandLineCodeResult: @@ -203,6 +255,9 @@ def _execute_code_dont_check_setup(self, code_blocks: List[CodeBlock]) -> Comman LocalCommandLineCodeExecutor.sanitize_command(lang, code) code = silence_pip(code, lang) + if lang in PYTHON_VARIANTS: + lang = "python" + if WIN32 and lang in ["sh", "shell"]: lang = "ps1" @@ -212,6 +267,7 @@ def _execute_code_dont_check_setup(self, code_blocks: List[CodeBlock]) -> Comman logs_all += "\n" + f"unknown language {lang}" break + execute_code = self.execution_policies.get(lang, False) try: # Check if there is a filename comment filename = _get_file_name_from_content(code, self._work_dir) @@ -222,18 +278,31 @@ def _execute_code_dont_check_setup(self, code_blocks: List[CodeBlock]) -> Comman # create a file with an automatically generated name code_hash = md5(code.encode()).hexdigest() filename = f"tmp_code_{code_hash}.{'py' if lang.startswith('python') else lang}" - written_file = (self._work_dir / filename).resolve() with written_file.open("w", encoding="utf-8") as f: f.write(code) file_names.append(written_file) - program = sys.executable if lang.startswith("python") else _cmd(lang) + if not execute_code: + # Just return a message that the file is saved. + logs_all += f"Code saved to {str(written_file)}\n" + exitcode = 0 + continue + + program = _cmd(lang) cmd = [program, str(written_file.absolute())] + env = os.environ.copy() + + if self._virtual_env_context: + path_with_virtualenv = rf"{self._virtual_env_context.bin_path}{os.pathsep}{env['PATH']}" + env["PATH"] = path_with_virtualenv + if WIN32: + activation_script = os.path.join(self._virtual_env_context.bin_path, "activate.bat") + cmd = [activation_script, "&&", *cmd] try: result = subprocess.run( - cmd, cwd=self._work_dir, capture_output=True, text=True, timeout=float(self._timeout) + cmd, cwd=self._work_dir, capture_output=True, text=True, timeout=float(self._timeout), env=env ) except subprocess.TimeoutExpired: logs_all += "\n" + TIMEOUT_MSG diff --git a/autogen/coding/markdown_code_extractor.py b/autogen/coding/markdown_code_extractor.py index 58ad4a09e2b..7a1084194eb 100644 --- a/autogen/coding/markdown_code_extractor.py +++ b/autogen/coding/markdown_code_extractor.py @@ -2,8 +2,8 @@ from typing import Any, Dict, List, Optional, Union from ..code_utils import CODE_BLOCK_PATTERN, UNKNOWN, content_str, infer_lang -from .base import CodeBlock, CodeExtractor from ..types import UserMessageImageContentPart, UserMessageTextContentPart +from .base import CodeBlock, CodeExtractor __all__ = ("MarkdownCodeExtractor",) diff --git a/autogen/coding/utils.py b/autogen/coding/utils.py index 0a7c5a7785d..d692bfe35b9 100644 --- a/autogen/coding/utils.py +++ b/autogen/coding/utils.py @@ -3,23 +3,31 @@ from pathlib import Path from typing import Optional +filename_patterns = [ + re.compile(r"^", re.DOTALL), + re.compile(r"^/\* (filename:)?(.+?) \*/", re.DOTALL), + re.compile(r"^// (filename:)?(.+?)$", re.DOTALL), + re.compile(r"^# (filename:)?(.+?)$", re.DOTALL), +] + # Raises ValueError if the file is not in the workspace def _get_file_name_from_content(code: str, workspace_path: Path) -> Optional[str]: - first_line = code.split("\n")[0] + first_line = code.split("\n")[0].strip() # TODO - support other languages - if first_line.startswith("# filename:"): - filename = first_line.split(":")[1].strip() - - # Handle relative paths in the filename - path = Path(filename) - if not path.is_absolute(): - path = workspace_path / path - path = path.resolve() - # Throws an error if the file is not in the workspace - relative = path.relative_to(workspace_path.resolve()) - return str(relative) + for pattern in filename_patterns: + matches = pattern.match(first_line) + if matches is not None: + filename = matches.group(2).strip() + # Handle relative paths in the filename + path = Path(filename) + if not path.is_absolute(): + path = workspace_path / path + path = path.resolve() + # Throws an error if the file is not in the workspace + relative = path.relative_to(workspace_path.resolve()) + return str(relative) return None diff --git a/autogen/function_utils.py b/autogen/function_utils.py index 0189836a494..dd225fd4719 100644 --- a/autogen/function_utils.py +++ b/autogen/function_utils.py @@ -73,7 +73,7 @@ def get_typed_return_annotation(call: Callable[..., Any]) -> Any: return get_typed_annotation(annotation, globalns) -def get_param_annotations(typed_signature: inspect.Signature) -> Dict[int, Union[Annotated[Type[Any], str], Type[Any]]]: +def get_param_annotations(typed_signature: inspect.Signature) -> Dict[str, Union[Annotated[Type[Any], str], Type[Any]]]: """Get the type annotations of the parameters of a function Args: @@ -110,9 +110,7 @@ class ToolFunction(BaseModel): function: Annotated[Function, Field(description="Function under tool")] -def get_parameter_json_schema( - k: str, v: Union[Annotated[Type[Any], str], Type[Any]], default_values: Dict[str, Any] -) -> JsonSchemaValue: +def get_parameter_json_schema(k: str, v: Any, default_values: Dict[str, Any]) -> JsonSchemaValue: """Get a JSON schema for a parameter as defined by the OpenAI API Args: @@ -285,7 +283,7 @@ def f(a: Annotated[str, "Parameter a"], b: int = 2, c: Annotated[float, "Paramet return model_dump(function) -def get_load_param_if_needed_function(t: Any) -> Optional[Callable[[T, Type[Any]], BaseModel]]: +def get_load_param_if_needed_function(t: Any) -> Optional[Callable[[Dict[str, Any], Type[BaseModel]], BaseModel]]: """Get a function to load a parameter if it is a Pydantic model Args: @@ -319,10 +317,10 @@ def load_basemodels_if_needed(func: Callable[..., Any]) -> Callable[..., Any]: param_annotations = get_param_annotations(typed_signature) # get functions for loading BaseModels when needed based on the type annotations - kwargs_mapping = {k: get_load_param_if_needed_function(t) for k, t in param_annotations.items()} + kwargs_mapping_with_nones = {k: get_load_param_if_needed_function(t) for k, t in param_annotations.items()} # remove the None values - kwargs_mapping = {k: f for k, f in kwargs_mapping.items() if f is not None} + kwargs_mapping = {k: f for k, f in kwargs_mapping_with_nones.items() if f is not None} # a function that loads the parameters before calling the original function @functools.wraps(func) diff --git a/autogen/graph_utils.py b/autogen/graph_utils.py index a84fc89f9cf..d36b47a12ed 100644 --- a/autogen/graph_utils.py +++ b/autogen/graph_utils.py @@ -1,7 +1,7 @@ -from typing import Dict, List import logging +from typing import Dict, List, Optional -from autogen.agentchat.groupchat import Agent +from autogen.agentchat import Agent def has_self_loops(allowed_speaker_transitions: Dict) -> bool: @@ -110,13 +110,15 @@ def invert_disallowed_to_allowed(disallowed_speaker_transitions_dict: dict, agen return allowed_speaker_transitions_dict -def visualize_speaker_transitions_dict(speaker_transitions_dict: dict, agents: List[Agent]): +def visualize_speaker_transitions_dict( + speaker_transitions_dict: dict, agents: List[Agent], export_path: Optional[str] = None +): """ Visualize the speaker_transitions_dict using networkx. """ try: - import networkx as nx import matplotlib.pyplot as plt + import networkx as nx except ImportError as e: logging.fatal("Failed to import networkx or matplotlib. Try running 'pip install autogen[graphs]'") raise e @@ -133,4 +135,8 @@ def visualize_speaker_transitions_dict(speaker_transitions_dict: dict, agents: L # Visualize nx.draw(G, with_labels=True, font_weight="bold") - plt.show() + + if export_path is not None: + plt.savefig(export_path) + else: + plt.show() diff --git a/autogen/io/__init__.py b/autogen/io/__init__.py index 20d6d5a578f..6bb8a35680f 100644 --- a/autogen/io/__init__.py +++ b/autogen/io/__init__.py @@ -3,6 +3,7 @@ from .websockets import IOWebsockets # Set the default input/output stream to the console -IOStream._default_io_stream.set(IOConsole()) +IOStream.set_global_default(IOConsole()) +IOStream.set_default(IOConsole()) __all__ = ("IOConsole", "IOStream", "InputStream", "OutputStream", "IOWebsockets") diff --git a/autogen/io/base.py b/autogen/io/base.py index 857d532e4f5..476e37db036 100644 --- a/autogen/io/base.py +++ b/autogen/io/base.py @@ -1,9 +1,12 @@ +import logging from contextlib import contextmanager from contextvars import ContextVar from typing import Any, Iterator, Optional, Protocol, runtime_checkable __all__ = ("OutputStream", "InputStream", "IOStream") +logger = logging.getLogger(__name__) + @runtime_checkable class OutputStream(Protocol): @@ -39,6 +42,31 @@ def input(self, prompt: str = "", *, password: bool = False) -> str: class IOStream(InputStream, OutputStream, Protocol): """A protocol for input/output streams.""" + # ContextVar must be used in multithreaded or async environments + _default_io_stream: ContextVar[Optional["IOStream"]] = ContextVar("default_iostream", default=None) + _default_io_stream.set(None) + _global_default: Optional["IOStream"] = None + + @staticmethod + def set_global_default(stream: "IOStream") -> None: + """Set the default input/output stream. + + Args: + stream (IOStream): The input/output stream to set as the default. + """ + IOStream._global_default = stream + + @staticmethod + def get_global_default() -> "IOStream": + """Get the default input/output stream. + + Returns: + IOStream: The default input/output stream. + """ + if IOStream._global_default is None: + raise RuntimeError("No global default IOStream has been set") + return IOStream._global_default + @staticmethod def get_default() -> "IOStream": """Get the default input/output stream. @@ -48,13 +76,11 @@ def get_default() -> "IOStream": """ iostream = IOStream._default_io_stream.get() if iostream is None: - raise RuntimeError("No default IOStream has been set") + iostream = IOStream.get_global_default() + # Set the default IOStream of the current context (thread/cooroutine) + IOStream.set_default(iostream) return iostream - # ContextVar must be used in multithreaded or async environments - _default_io_stream: ContextVar[Optional["IOStream"]] = ContextVar("default_iostream") - _default_io_stream.set(None) - @staticmethod @contextmanager def set_default(stream: Optional["IOStream"]) -> Iterator[None]: diff --git a/autogen/io/websockets.py b/autogen/io/websockets.py index 45caffcdcc2..9d38a718754 100644 --- a/autogen/io/websockets.py +++ b/autogen/io/websockets.py @@ -4,7 +4,7 @@ from contextlib import contextmanager from functools import partial from time import sleep -from typing import Any, Callable, Dict, Iterable, Iterator, Optional, TYPE_CHECKING, Protocol, Union +from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, Iterator, Optional, Protocol, Union from .base import IOStream diff --git a/autogen/logger/base_logger.py b/autogen/logger/base_logger.py index 97508b4883c..7c35f8a5091 100644 --- a/autogen/logger/base_logger.py +++ b/autogen/logger/base_logger.py @@ -1,15 +1,15 @@ from __future__ import annotations -from abc import ABC, abstractmethod -from typing import Any, Dict, List, TYPE_CHECKING, Union import sqlite3 import uuid +from abc import ABC, abstractmethod +from typing import TYPE_CHECKING, Any, Dict, List, Union -from openai import OpenAI, AzureOpenAI +from openai import AzureOpenAI, OpenAI from openai.types.chat import ChatCompletion if TYPE_CHECKING: - from autogen import ConversableAgent, OpenAIWrapper + from autogen import Agent, ConversableAgent, OpenAIWrapper ConfigItem = Dict[str, Union[str, List[str]]] LLMConfig = Dict[str, Union[None, float, int, ConfigItem, List[ConfigItem]]] @@ -68,6 +68,18 @@ def log_new_agent(self, agent: ConversableAgent, init_args: Dict[str, Any]) -> N """ ... + @abstractmethod + def log_event(self, source: Union[str, Agent], name: str, **kwargs: Dict[str, Any]) -> None: + """ + Log an event for an agent. + + Args: + source (str or Agent): The source/creator of the event as a string name or an Agent instance + name (str): The name of the event + kwargs (dict): The event information to log + """ + ... + @abstractmethod def log_new_wrapper(self, wrapper: OpenAIWrapper, init_args: Dict[str, Union[LLMConfig, List[LLMConfig]]]) -> None: """ diff --git a/autogen/logger/file_logger.py b/autogen/logger/file_logger.py new file mode 100644 index 00000000000..f8578474958 --- /dev/null +++ b/autogen/logger/file_logger.py @@ -0,0 +1,211 @@ +from __future__ import annotations + +import json +import logging +import os +import threading +import uuid +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union + +from openai import AzureOpenAI, OpenAI +from openai.types.chat import ChatCompletion + +from autogen.logger.base_logger import BaseLogger +from autogen.logger.logger_utils import get_current_ts, to_dict + +from .base_logger import LLMConfig + +if TYPE_CHECKING: + from autogen import Agent, ConversableAgent, OpenAIWrapper + +logger = logging.getLogger(__name__) + + +class FileLogger(BaseLogger): + def __init__(self, config: Dict[str, Any]): + self.config = config + self.session_id = str(uuid.uuid4()) + + curr_dir = os.getcwd() + self.log_dir = os.path.join(curr_dir, "autogen_logs") + os.makedirs(self.log_dir, exist_ok=True) + + self.log_file = os.path.join(self.log_dir, self.config.get("filename", "runtime.log")) + try: + with open(self.log_file, "a"): + pass + except Exception as e: + logger.error(f"[file_logger] Failed to create logging file: {e}") + + self.logger = logging.getLogger(__name__) + self.logger.setLevel(logging.INFO) + file_handler = logging.FileHandler(self.log_file) + self.logger.addHandler(file_handler) + + def start(self) -> str: + """Start the logger and return the session_id.""" + try: + self.logger.info(f"Started new session with Session ID: {self.session_id}") + except Exception as e: + logger.error(f"[file_logger] Failed to create logging file: {e}") + finally: + return self.session_id + + def log_chat_completion( + self, + invocation_id: uuid.UUID, + client_id: int, + wrapper_id: int, + request: Dict[str, Union[float, str, List[Dict[str, str]]]], + response: Union[str, ChatCompletion], + is_cached: int, + cost: float, + start_time: str, + ) -> None: + """ + Log a chat completion. + """ + thread_id = threading.get_ident() + try: + log_data = json.dumps( + { + "invocation_id": str(invocation_id), + "client_id": client_id, + "wrapper_id": wrapper_id, + "request": to_dict(request), + "response": str(response), + "is_cached": is_cached, + "cost": cost, + "start_time": start_time, + "end_time": get_current_ts(), + "thread_id": thread_id, + } + ) + + self.logger.info(log_data) + except Exception as e: + self.logger.error(f"[file_logger] Failed to log chat completion: {e}") + + def log_new_agent(self, agent: ConversableAgent, init_args: Dict[str, Any] = {}) -> None: + """ + Log a new agent instance. + """ + thread_id = threading.get_ident() + + try: + log_data = json.dumps( + { + "id": id(agent), + "agent_name": agent.name if hasattr(agent, "name") and agent.name is not None else "", + "wrapper_id": to_dict( + agent.client.wrapper_id if hasattr(agent, "client") and agent.client is not None else "" + ), + "session_id": self.session_id, + "current_time": get_current_ts(), + "agent_type": type(agent).__name__, + "args": to_dict(init_args), + "thread_id": thread_id, + } + ) + self.logger.info(log_data) + except Exception as e: + self.logger.error(f"[file_logger] Failed to log new agent: {e}") + + def log_event(self, source: Union[str, Agent], name: str, **kwargs: Dict[str, Any]) -> None: + """ + Log an event from an agent or a string source. + """ + from autogen import Agent + + # This takes an object o as input and returns a string. If the object o cannot be serialized, instead of raising an error, + # it returns a string indicating that the object is non-serializable, along with its type's qualified name obtained using __qualname__. + json_args = json.dumps(kwargs, default=lambda o: f"<>") + thread_id = threading.get_ident() + + if isinstance(source, Agent): + try: + log_data = json.dumps( + { + "source_id": id(source), + "source_name": str(source.name) if hasattr(source, "name") else source, + "event_name": name, + "agent_module": source.__module__, + "agent_class": source.__class__.__name__, + "json_state": json_args, + "timestamp": get_current_ts(), + "thread_id": thread_id, + } + ) + self.logger.info(log_data) + except Exception as e: + self.logger.error(f"[file_logger] Failed to log event {e}") + else: + try: + log_data = json.dumps( + { + "source_id": id(source), + "source_name": str(source.name) if hasattr(source, "name") else source, + "event_name": name, + "json_state": json_args, + "timestamp": get_current_ts(), + "thread_id": thread_id, + } + ) + self.logger.info(log_data) + except Exception as e: + self.logger.error(f"[file_logger] Failed to log event {e}") + + def log_new_wrapper( + self, wrapper: OpenAIWrapper, init_args: Dict[str, Union[LLMConfig, List[LLMConfig]]] = {} + ) -> None: + """ + Log a new wrapper instance. + """ + thread_id = threading.get_ident() + + try: + log_data = json.dumps( + { + "wrapper_id": id(wrapper), + "session_id": self.session_id, + "json_state": json.dumps(init_args), + "timestamp": get_current_ts(), + "thread_id": thread_id, + } + ) + self.logger.info(log_data) + except Exception as e: + self.logger.error(f"[file_logger] Failed to log event {e}") + + def log_new_client(self, client: AzureOpenAI | OpenAI, wrapper: OpenAIWrapper, init_args: Dict[str, Any]) -> None: + """ + Log a new client instance. + """ + thread_id = threading.get_ident() + + try: + log_data = json.dumps( + { + "client_id": id(client), + "wrapper_id": id(wrapper), + "session_id": self.session_id, + "class": type(client).__name__, + "json_state": json.dumps(init_args), + "timestamp": get_current_ts(), + "thread_id": thread_id, + } + ) + self.logger.info(log_data) + except Exception as e: + self.logger.error(f"[file_logger] Failed to log event {e}") + + def get_connection(self) -> None: + """Method is intentionally left blank because there is no specific connection needed for the FileLogger.""" + pass + + def stop(self) -> None: + """Close the file handler and remove it from the logger.""" + for handler in self.logger.handlers: + if isinstance(handler, logging.FileHandler): + handler.close() + self.logger.removeHandler(handler) diff --git a/autogen/logger/logger_factory.py b/autogen/logger/logger_factory.py index 282efc3263e..ed9567977bb 100644 --- a/autogen/logger/logger_factory.py +++ b/autogen/logger/logger_factory.py @@ -1,5 +1,7 @@ -from typing import Any, Dict, Optional +from typing import Any, Dict, Literal, Optional + from autogen.logger.base_logger import BaseLogger +from autogen.logger.file_logger import FileLogger from autogen.logger.sqlite_logger import SqliteLogger __all__ = ("LoggerFactory",) @@ -7,11 +9,15 @@ class LoggerFactory: @staticmethod - def get_logger(logger_type: str = "sqlite", config: Optional[Dict[str, Any]] = None) -> BaseLogger: + def get_logger( + logger_type: Literal["sqlite", "file"] = "sqlite", config: Optional[Dict[str, Any]] = None + ) -> BaseLogger: if config is None: config = {} if logger_type == "sqlite": return SqliteLogger(config) + elif logger_type == "file": + return FileLogger(config) else: raise ValueError(f"[logger_factory] Unknown logger type: {logger_type}") diff --git a/autogen/logger/sqlite_logger.py b/autogen/logger/sqlite_logger.py index 227d57f91ee..6e95a571cd0 100644 --- a/autogen/logger/sqlite_logger.py +++ b/autogen/logger/sqlite_logger.py @@ -6,18 +6,18 @@ import sqlite3 import threading import uuid +from typing import TYPE_CHECKING, Any, Dict, List, Tuple, Union + +from openai import AzureOpenAI, OpenAI +from openai.types.chat import ChatCompletion from autogen.logger.base_logger import BaseLogger from autogen.logger.logger_utils import get_current_ts, to_dict -from openai import OpenAI, AzureOpenAI -from openai.types.chat import ChatCompletion -from typing import Any, Dict, List, TYPE_CHECKING, Tuple, Union from .base_logger import LLMConfig - if TYPE_CHECKING: - from autogen import ConversableAgent, OpenAIWrapper + from autogen import Agent, ConversableAgent, OpenAIWrapper logger = logging.getLogger(__name__) lock = threading.Lock() @@ -103,6 +103,20 @@ class TEXT, -- type or class name of cli """ self._run_query(query=query) + query = """ + CREATE TABLE IF NOT EXISTS events ( + event_name TEXT, + source_id INTEGER, + source_name TEXT, + agent_module TEXT DEFAULT NULL, + agent_class_name TEXT DEFAULT NULL, + id INTEGER PRIMARY KEY, + json_state TEXT, + timestamp DATETIME DEFAULT CURRENT_TIMESTAMP + ); + """ + self._run_query(query=query) + current_verion = self._get_current_db_version() if current_verion is None: self._run_query( @@ -246,6 +260,41 @@ class = excluded.class, ) self._run_query(query=query, args=args) + def log_event(self, source: Union[str, Agent], name: str, **kwargs: Dict[str, Any]) -> None: + from autogen import Agent + + if self.con is None: + return + + json_args = json.dumps(kwargs, default=lambda o: f"<>") + + if isinstance(source, Agent): + query = """ + INSERT INTO events (source_id, source_name, event_name, agent_module, agent_class_name, json_state, timestamp) VALUES (?, ?, ?, ?, ?, ?, ?) + """ + args = ( + id(source), + source.name if hasattr(source, "name") else source, + name, + source.__module__, + source.__class__.__name__, + json_args, + get_current_ts(), + ) + self._run_query(query=query, args=args) + else: + query = """ + INSERT INTO events (source_id, source_name, event_name, json_state, timestamp) VALUES (?, ?, ?, ?, ?) + """ + args_str_based = ( + id(source), + source.name if hasattr(source, "name") else source, + name, + json_args, + get_current_ts(), + ) + self._run_query(query=query, args=args_str_based) + def log_new_wrapper(self, wrapper: OpenAIWrapper, init_args: Dict[str, Union[LLMConfig, List[LLMConfig]]]) -> None: if self.con is None: return diff --git a/autogen/math_utils.py b/autogen/math_utils.py index 00fcae57ad2..aeac2fc9715 100644 --- a/autogen/math_utils.py +++ b/autogen/math_utils.py @@ -1,5 +1,6 @@ from typing import Optional -from autogen import oai, DEFAULT_MODEL + +from autogen import DEFAULT_MODEL, oai _MATH_PROMPT = "{problem} Solve the problem carefully. Simplify your answer as much as possible. Put the final answer in \\boxed{{}}." _MATH_CONFIG = { diff --git a/autogen/oai/__init__.py b/autogen/oai/__init__.py index 9e8437cecc7..d5e2000f759 100644 --- a/autogen/oai/__init__.py +++ b/autogen/oai/__init__.py @@ -1,15 +1,15 @@ -from autogen.oai.client import OpenAIWrapper, ModelClient -from autogen.oai.completion import Completion, ChatCompletion +from autogen.cache.cache import Cache +from autogen.oai.client import ModelClient, OpenAIWrapper +from autogen.oai.completion import ChatCompletion, Completion from autogen.oai.openai_utils import ( - get_config_list, + config_list_from_dotenv, + config_list_from_json, + config_list_from_models, config_list_gpt4_gpt35, config_list_openai_aoai, - config_list_from_models, - config_list_from_json, - config_list_from_dotenv, filter_config, + get_config_list, ) -from autogen.cache.cache import Cache __all__ = [ "OpenAIWrapper", diff --git a/autogen/oai/client.py b/autogen/oai/client.py index f288ece3961..3edfa40d4ec 100644 --- a/autogen/oai/client.py +++ b/autogen/oai/client.py @@ -1,22 +1,20 @@ from __future__ import annotations -import sys -from typing import Any, List, Optional, Dict, Callable, Tuple, Union -import logging import inspect +import logging +import sys import uuid -from flaml.automl.logger import logger_formatter +from typing import Any, Callable, Dict, List, Optional, Protocol, Tuple, Union +from flaml.automl.logger import logger_formatter from pydantic import BaseModel -from typing import Protocol from autogen.cache import Cache from autogen.io.base import IOStream -from autogen.oai.openai_utils import get_key, is_valid_api_key, OAI_PRICE1K -from autogen.token_count_utils import count_token - -from autogen.runtime_logging import logging_enabled, log_chat_completion, log_new_client, log_new_wrapper from autogen.logger.logger_utils import get_current_ts +from autogen.oai.openai_utils import OAI_PRICE1K, get_key, is_valid_api_key +from autogen.runtime_logging import log_chat_completion, log_new_client, log_new_wrapper, logging_enabled +from autogen.token_count_utils import count_token TOOL_ENABLED = False try: @@ -27,14 +25,15 @@ AzureOpenAI = object else: # raises exception if openai>=1 is installed and something is wrong with imports - from openai import OpenAI, AzureOpenAI, APIError, APITimeoutError, __version__ as OPENAIVERSION + from openai import APIError, APITimeoutError, AzureOpenAI, OpenAI + from openai import __version__ as OPENAIVERSION from openai.resources import Completions from openai.types.chat import ChatCompletion from openai.types.chat.chat_completion import ChatCompletionMessage, Choice # type: ignore [attr-defined] from openai.types.chat.chat_completion_chunk import ( + ChoiceDeltaFunctionCall, ChoiceDeltaToolCall, ChoiceDeltaToolCallFunction, - ChoiceDeltaFunctionCall, ) from openai.types.completion import Completion from openai.types.completion_usage import CompletionUsage @@ -43,6 +42,13 @@ TOOL_ENABLED = True ERROR = None +try: + from autogen.oai.gemini import GeminiClient + + gemini_import_exception: Optional[ImportError] = None +except ImportError as e: + gemini_import_exception = e + logger = logging.getLogger(__name__) if not logger.handlers: # Add the console handler. @@ -290,6 +296,8 @@ def cost(self, response: Union[ChatCompletion, Completion]) -> float: n_input_tokens = response.usage.prompt_tokens if response.usage is not None else 0 # type: ignore [union-attr] n_output_tokens = response.usage.completion_tokens if response.usage is not None else 0 # type: ignore [union-attr] + if n_output_tokens is None: + n_output_tokens = 0 tmp_price1K = OAI_PRICE1K[model] # First value is input token rate, second value is output token rate if isinstance(tmp_price1K, tuple): @@ -424,6 +432,10 @@ def _register_default_client(self, config: Dict[str, Any], openai_config: Dict[s self._configure_azure_openai(config, openai_config) client = AzureOpenAI(**openai_config) self._clients.append(OpenAIClient(client)) + elif api_type is not None and api_type.startswith("google"): + if gemini_import_exception: + raise ImportError("Please install `google-generativeai` to use Google OpenAI API.") + self._clients.append(GeminiClient(**openai_config)) else: client = OpenAI(**openai_config) self._clients.append(OpenAIClient(client)) @@ -806,6 +818,8 @@ def update_usage(usage_summary, response_usage): cost = response_usage["cost"] prompt_tokens = response_usage["prompt_tokens"] completion_tokens = response_usage["completion_tokens"] + if completion_tokens is None: + completion_tokens = 0 total_tokens = response_usage["total_tokens"] if usage_summary is None: diff --git a/autogen/oai/completion.py b/autogen/oai/completion.py index 43ccd0b3bc2..e3b01ee4dd8 100644 --- a/autogen/oai/completion.py +++ b/autogen/oai/completion.py @@ -1,28 +1,30 @@ -from time import sleep import logging -import time -from typing import List, Optional, Dict, Callable, Union -import sys import shutil +import sys +import time +from collections import defaultdict +from time import sleep +from typing import Callable, Dict, List, Optional, Union + import numpy as np -from flaml import tune, BlendSearch -from flaml.tune.space import is_constant +from flaml import BlendSearch, tune from flaml.automl.logger import logger_formatter +from flaml.tune.space import is_constant + from .openai_utils import get_key -from collections import defaultdict try: + import diskcache import openai from openai import ( - RateLimitError, + APIConnectionError, APIError, + AuthenticationError, BadRequestError, - APIConnectionError, + RateLimitError, Timeout, - AuthenticationError, ) from openai import Completion as openai_Completion - import diskcache ERROR = None assert openai.__version__ < "1" diff --git a/autogen/oai/gemini.py b/autogen/oai/gemini.py new file mode 100644 index 00000000000..fcf7e09c025 --- /dev/null +++ b/autogen/oai/gemini.py @@ -0,0 +1,310 @@ +"""Create a OpenAI-compatible client for Gemini features. + + +Example: + llm_config={ + "config_list": [{ + "api_type": "google", + "model": "models/gemini-pro", + "api_key": os.environ.get("GOOGLE_API_KEY") + } + ]} + + agent = autogen.AssistantAgent("my_agent", llm_config=llm_config) + +Resources: +- https://ai.google.dev/docs +- https://cloud.google.com/vertex-ai/docs/generative-ai/migrate-from-azure +- https://blog.google/technology/ai/google-gemini-pro-imagen-duet-ai-update/ +- https://ai.google.dev/api/python/google/generativeai/ChatSession +""" + +from __future__ import annotations + +import base64 +import os +import random +import re +import time +import warnings +from io import BytesIO +from typing import Any, Dict, List, Mapping, Union + +import google.generativeai as genai +import requests +from google.ai.generativelanguage import Content, Part +from google.api_core.exceptions import InternalServerError +from openai.types.chat import ChatCompletion +from openai.types.chat.chat_completion import ChatCompletionMessage, Choice +from openai.types.completion_usage import CompletionUsage +from PIL import Image + + +class GeminiClient: + """Client for Google's Gemini API. + + Please visit this [page](https://github.com/microsoft/autogen/issues/2387) for the roadmap of Gemini integration + of AutoGen. + """ + + def __init__(self, **kwargs): + self.api_key = kwargs.get("api_key", None) + if not self.api_key: + self.api_key = os.getenv("GOOGLE_API_KEY") + + assert ( + self.api_key + ), "Please provide api_key in your config list entry for Gemini or set the GOOGLE_API_KEY env variable." + + def message_retrieval(self, response) -> List: + """ + Retrieve and return a list of strings or a list of Choice.Message from the response. + + NOTE: if a list of Choice.Message is returned, it currently needs to contain the fields of OpenAI's ChatCompletion Message object, + since that is expected for function or tool calling in the rest of the codebase at the moment, unless a custom agent is being used. + """ + return [choice.message for choice in response.choices] + + def cost(self, response) -> float: + return response.cost + + @staticmethod + def get_usage(response) -> Dict: + """Return usage summary of the response using RESPONSE_USAGE_KEYS.""" + # ... # pragma: no cover + return { + "prompt_tokens": response.usage.prompt_tokens, + "completion_tokens": response.usage.completion_tokens, + "total_tokens": response.usage.total_tokens, + "cost": response.cost, + "model": response.model, + } + + def create(self, params: Dict) -> ChatCompletion: + model_name = params.get("model", "gemini-pro") + if not model_name: + raise ValueError( + "Please provide a model name for the Gemini Client. " + "You can configurate it in the OAI Config List file. " + "See this [LLM configuration tutorial](https://microsoft.github.io/autogen/docs/topics/llm_configuration/) for more details." + ) + + params.get("api_type", "google") # not used + messages = params.get("messages", []) + stream = params.get("stream", False) + n_response = params.get("n", 1) + params.get("temperature", 0.5) + params.get("top_p", 1.0) + params.get("max_tokens", 4096) + + if stream: + # warn user that streaming is not supported + warnings.warn( + "Streaming is not supported for Gemini yet, and it will have no effect. Please set stream=False.", + UserWarning, + ) + + if n_response > 1: + warnings.warn("Gemini only supports `n=1` for now. We only generate one response.", UserWarning) + + if "vision" not in model_name: + # A. create and call the chat model. + gemini_messages = oai_messages_to_gemini_messages(messages) + + # we use chat model by default + model = genai.GenerativeModel(model_name) + genai.configure(api_key=self.api_key) + chat = model.start_chat(history=gemini_messages[:-1]) + max_retries = 5 + for attempt in range(max_retries): + ans = None + try: + response = chat.send_message(gemini_messages[-1].parts[0].text, stream=stream) + except InternalServerError: + delay = 5 * (2**attempt) + warnings.warn( + f"InternalServerError `500` occurs when calling Gemini's chat model. Retry in {delay} seconds...", + UserWarning, + ) + time.sleep(delay) + except Exception as e: + raise RuntimeError(f"Google GenAI exception occurred while calling Gemini API: {e}") + else: + # `ans = response.text` is unstable. Use the following code instead. + ans: str = chat.history[-1].parts[0].text + break + + if ans is None: + raise RuntimeError(f"Fail to get response from Google AI after retrying {attempt + 1} times.") + + prompt_tokens = model.count_tokens(chat.history[:-1]).total_tokens + completion_tokens = model.count_tokens(ans).total_tokens + elif model_name == "gemini-pro-vision": + # B. handle the vision model + # Gemini's vision model does not support chat history yet + model = genai.GenerativeModel(model_name) + genai.configure(api_key=self.api_key) + # chat = model.start_chat(history=gemini_messages[:-1]) + # response = chat.send_message(gemini_messages[-1]) + user_message = oai_content_to_gemini_content(messages[-1]["content"]) + if len(messages) > 2: + warnings.warn( + "Warning: Gemini's vision model does not support chat history yet.", + "We only use the last message as the prompt.", + UserWarning, + ) + + response = model.generate_content(user_message, stream=stream) + # ans = response.text + ans: str = response._result.candidates[0].content.parts[0].text + + prompt_tokens = model.count_tokens(user_message).total_tokens + completion_tokens = model.count_tokens(ans).total_tokens + + # 3. convert output + message = ChatCompletionMessage(role="assistant", content=ans, function_call=None, tool_calls=None) + choices = [Choice(finish_reason="stop", index=0, message=message)] + + response_oai = ChatCompletion( + id=str(random.randint(0, 1000)), + model=model_name, + created=int(time.time() * 1000), + object="chat.completion", + choices=choices, + usage=CompletionUsage( + prompt_tokens=prompt_tokens, + completion_tokens=completion_tokens, + total_tokens=prompt_tokens + completion_tokens, + ), + cost=calculate_gemini_cost(prompt_tokens, completion_tokens, model_name), + ) + + return response_oai + + +def calculate_gemini_cost(input_tokens: int, output_tokens: int, model_name: str) -> float: + if "1.5" in model_name or "gemini-experimental" in model_name: + # "gemini-1.5-pro-preview-0409" + # Cost is $7 per million input tokens and $21 per million output tokens + return 7.0 * input_tokens / 1e6 + 21.0 * output_tokens / 1e6 + + if "gemini-pro" not in model_name and "gemini-1.0-pro" not in model_name: + warnings.warn(f"Cost calculation is not implemented for model {model_name}. Using Gemini-1.0-Pro.", UserWarning) + + # Cost is $0.5 per million input tokens and $1.5 per million output tokens + return 0.5 * input_tokens / 1e6 + 1.5 * output_tokens / 1e6 + + +def oai_content_to_gemini_content(content: Union[str, List]) -> List: + """Convert content from OAI format to Gemini format""" + rst = [] + if isinstance(content, str): + rst.append(Part(text=content)) + return rst + + assert isinstance(content, list) + + for msg in content: + if isinstance(msg, dict): + assert "type" in msg, f"Missing 'type' field in message: {msg}" + if msg["type"] == "text": + rst.append(Part(text=msg["text"])) + elif msg["type"] == "image_url": + b64_img = get_image_data(msg["image_url"]["url"]) + img = _to_pil(b64_img) + rst.append(img) + else: + raise ValueError(f"Unsupported message type: {msg['type']}") + else: + raise ValueError(f"Unsupported message type: {type(msg)}") + return rst + + +def concat_parts(parts: List[Part]) -> List: + """Concatenate parts with the same type. + If two adjacent parts both have the "text" attribute, then it will be joined into one part. + """ + if not parts: + return [] + + concatenated_parts = [] + previous_part = parts[0] + + for current_part in parts[1:]: + if previous_part.text != "": + previous_part.text += current_part.text + else: + concatenated_parts.append(previous_part) + previous_part = current_part + + if previous_part.text == "": + previous_part.text = "empty" # Empty content is not allowed. + concatenated_parts.append(previous_part) + + return concatenated_parts + + +def oai_messages_to_gemini_messages(messages: list[Dict[str, Any]]) -> list[dict[str, Any]]: + """Convert messages from OAI format to Gemini format. + Make sure the "user" role and "model" role are interleaved. + Also, make sure the last item is from the "user" role. + """ + prev_role = None + rst = [] + curr_parts = [] + for i, message in enumerate(messages): + parts = oai_content_to_gemini_content(message["content"]) + role = "user" if message["role"] in ["user", "system"] else "model" + + if prev_role is None or role == prev_role: + curr_parts += parts + elif role != prev_role: + rst.append(Content(parts=concat_parts(curr_parts), role=prev_role)) + curr_parts = parts + prev_role = role + + # handle the last message + rst.append(Content(parts=concat_parts(curr_parts), role=role)) + + # The Gemini is restrict on order of roles, such that + # 1. The messages should be interleaved between user and model. + # 2. The last message must be from the user role. + # We add a dummy message "continue" if the last role is not the user. + if rst[-1].role != "user": + rst.append(Content(parts=oai_content_to_gemini_content("continue"), role="user")) + + return rst + + +def _to_pil(data: str) -> Image.Image: + """ + Converts a base64 encoded image data string to a PIL Image object. + + This function first decodes the base64 encoded string to bytes, then creates a BytesIO object from the bytes, + and finally creates and returns a PIL Image object from the BytesIO object. + + Parameters: + data (str): The base64 encoded image data string. + + Returns: + Image.Image: The PIL Image object created from the input data. + """ + return Image.open(BytesIO(base64.b64decode(data))) + + +def get_image_data(image_file: str, use_b64=True) -> bytes: + if image_file.startswith("http://") or image_file.startswith("https://"): + response = requests.get(image_file) + content = response.content + elif re.match(r"data:image/(?:png|jpeg);base64,", image_file): + return re.sub(r"data:image/(?:png|jpeg);base64,", "", image_file) + else: + image = Image.open(image_file).convert("RGB") + buffered = BytesIO() + image.save(buffered, format="PNG") + content = buffered.getvalue() + + if use_b64: + return base64.b64encode(content).decode("utf-8") + else: + return content diff --git a/autogen/oai/openai_utils.py b/autogen/oai/openai_utils.py index 411ac03f003..f8dad6d7984 100644 --- a/autogen/oai/openai_utils.py +++ b/autogen/oai/openai_utils.py @@ -1,19 +1,46 @@ +import importlib.metadata import json import logging import os import re import tempfile +import time from pathlib import Path from typing import Any, Dict, List, Optional, Set, Union from dotenv import find_dotenv, load_dotenv - from openai import OpenAI from openai.types.beta.assistant import Assistant +from packaging.version import parse NON_CACHE_KEY = ["api_key", "base_url", "api_type", "api_version"] DEFAULT_AZURE_API_VERSION = "2024-02-15-preview" OAI_PRICE1K = { + # https://openai.com/api/pricing/ + # gpt-4o + "gpt-4o": (0.005, 0.015), + "gpt-4o-2024-05-13": (0.005, 0.015), + # gpt-4-turbo + "gpt-4-turbo-2024-04-09": (0.01, 0.03), + # gpt-4 + "gpt-4": (0.03, 0.06), + "gpt-4-32k": (0.06, 0.12), + # gpt-3.5 turbo + "gpt-3.5-turbo": (0.0005, 0.0015), # default is 0125 + "gpt-3.5-turbo-0125": (0.0005, 0.0015), # 16k + "gpt-3.5-turbo-instruct": (0.0015, 0.002), + # base model + "davinci-002": 0.002, + "babbage-002": 0.0004, + # old model + "gpt-4-0125-preview": (0.01, 0.03), + "gpt-4-1106-preview": (0.01, 0.03), + "gpt-4-1106-vision-preview": (0.01, 0.03), # TODO: support vision pricing of images + "gpt-3.5-turbo-1106": (0.001, 0.002), + "gpt-3.5-turbo-0613": (0.0015, 0.002), + # "gpt-3.5-turbo-16k": (0.003, 0.004), + "gpt-3.5-turbo-16k-0613": (0.003, 0.004), + "gpt-3.5-turbo-0301": (0.0015, 0.002), "text-ada-001": 0.0004, "text-babbage-001": 0.0005, "text-curie-001": 0.002, @@ -21,28 +48,20 @@ "code-davinci-002": 0.1, "text-davinci-002": 0.02, "text-davinci-003": 0.02, - "gpt-3.5-turbo-instruct": (0.0015, 0.002), - "gpt-3.5-turbo-0301": (0.0015, 0.002), # deprecate in Sep - "gpt-3.5-turbo-0613": (0.0015, 0.002), - "gpt-3.5-turbo-16k": (0.003, 0.004), - "gpt-3.5-turbo-16k-0613": (0.003, 0.004), - "gpt-35-turbo": (0.0015, 0.002), - "gpt-35-turbo-16k": (0.003, 0.004), - "gpt-35-turbo-instruct": (0.0015, 0.002), - "gpt-4": (0.03, 0.06), - "gpt-4-32k": (0.06, 0.12), "gpt-4-0314": (0.03, 0.06), # deprecate in Sep "gpt-4-32k-0314": (0.06, 0.12), # deprecate in Sep "gpt-4-0613": (0.03, 0.06), "gpt-4-32k-0613": (0.06, 0.12), - # 11-06 - "gpt-3.5-turbo": (0.0015, 0.002), # default is still 0613 - "gpt-3.5-turbo-1106": (0.001, 0.002), - "gpt-35-turbo-1106": (0.001, 0.002), - "gpt-4-1106-preview": (0.01, 0.03), - "gpt-4-0125-preview": (0.01, 0.03), "gpt-4-turbo-preview": (0.01, 0.03), - "gpt-4-1106-vision-preview": (0.01, 0.03), # TODO: support vision pricing of images + # https://azure.microsoft.com/en-us/pricing/details/cognitive-services/openai-service/#pricing + "gpt-35-turbo": (0.0005, 0.0015), # what's the default? using 0125 here. + "gpt-35-turbo-0125": (0.0005, 0.0015), + "gpt-35-turbo-instruct": (0.0015, 0.002), + "gpt-35-turbo-1106": (0.001, 0.002), + "gpt-35-turbo-0613": (0.0015, 0.002), + "gpt-35-turbo-0301": (0.0015, 0.002), + "gpt-35-turbo-16k": (0.003, 0.004), + "gpt-35-turbo-16k-0613": (0.003, 0.004), } @@ -77,7 +96,7 @@ def is_valid_api_key(api_key: str) -> bool: Returns: bool: A boolean that indicates if input is valid OpenAI API key. """ - api_key_re = re.compile(r"^sk-[A-Za-z0-9]{32,}$") + api_key_re = re.compile(r"^sk-(proj-)?[A-Za-z0-9]{32,}$") return bool(re.fullmatch(api_key_re, api_key)) @@ -541,11 +560,11 @@ def get_config( """ config = {"api_key": api_key} if base_url: - config["base_url"] = base_url + config["base_url"] = os.getenv(base_url, default=base_url) if api_type: - config["api_type"] = api_type + config["api_type"] = os.getenv(api_type, default=api_type) if api_version: - config["api_version"] = api_version + config["api_version"] = os.getenv(api_version, default=api_version) return config @@ -662,3 +681,107 @@ def retrieve_assistants_by_name(client: OpenAI, name: str) -> List[Assistant]: if assistant.name == name: candidate_assistants.append(assistant) return candidate_assistants + + +def detect_gpt_assistant_api_version() -> str: + """Detect the openai assistant API version""" + oai_version = importlib.metadata.version("openai") + if parse(oai_version) < parse("1.21"): + return "v1" + else: + return "v2" + + +def create_gpt_vector_store(client: OpenAI, name: str, fild_ids: List[str]) -> Any: + """Create a openai vector store for gpt assistant""" + + try: + vector_store = client.beta.vector_stores.create(name=name) + except Exception as e: + raise AttributeError(f"Failed to create vector store, please install the latest OpenAI python package: {e}") + + # poll the status of the file batch for completion. + batch = client.beta.vector_stores.file_batches.create_and_poll(vector_store_id=vector_store.id, file_ids=fild_ids) + + if batch.status == "in_progress": + time.sleep(1) + logging.debug(f"file batch status: {batch.file_counts}") + batch = client.beta.vector_stores.file_batches.poll(vector_store_id=vector_store.id, batch_id=batch.id) + + if batch.status == "completed": + return vector_store + + raise ValueError(f"Failed to upload files to vector store {vector_store.id}:{batch.status}") + + +def create_gpt_assistant( + client: OpenAI, name: str, instructions: str, model: str, assistant_config: Dict[str, Any] +) -> Assistant: + """Create a openai gpt assistant""" + + assistant_create_kwargs = {} + gpt_assistant_api_version = detect_gpt_assistant_api_version() + tools = assistant_config.get("tools", []) + + if gpt_assistant_api_version == "v2": + tool_resources = assistant_config.get("tool_resources", {}) + file_ids = assistant_config.get("file_ids") + if tool_resources.get("file_search") is not None and file_ids is not None: + raise ValueError( + "Cannot specify both `tool_resources['file_search']` tool and `file_ids` in the assistant config." + ) + + # Designed for backwards compatibility for the V1 API + # Instead of V1 AssistantFile, files are attached to Assistants using the tool_resources object. + for tool in tools: + if tool["type"] == "retrieval": + tool["type"] = "file_search" + if file_ids is not None: + # create a vector store for the file search tool + vs = create_gpt_vector_store(client, f"{name}-vectorestore", file_ids) + tool_resources["file_search"] = { + "vector_store_ids": [vs.id], + } + elif tool["type"] == "code_interpreter" and file_ids is not None: + tool_resources["code_interpreter"] = { + "file_ids": file_ids, + } + + assistant_create_kwargs["tools"] = tools + if len(tool_resources) > 0: + assistant_create_kwargs["tool_resources"] = tool_resources + else: + # not support forwards compatibility + if "tool_resources" in assistant_config: + raise ValueError("`tool_resources` argument are not supported in the openai assistant V1 API.") + if any(tool["type"] == "file_search" for tool in tools): + raise ValueError( + "`file_search` tool are not supported in the openai assistant V1 API, please use `retrieval`." + ) + assistant_create_kwargs["tools"] = tools + assistant_create_kwargs["file_ids"] = assistant_config.get("file_ids", []) + + logging.info(f"Creating assistant with config: {assistant_create_kwargs}") + return client.beta.assistants.create(name=name, instructions=instructions, model=model, **assistant_create_kwargs) + + +def update_gpt_assistant(client: OpenAI, assistant_id: str, assistant_config: Dict[str, Any]) -> Assistant: + """Update openai gpt assistant""" + + gpt_assistant_api_version = detect_gpt_assistant_api_version() + assistant_update_kwargs = {} + + if assistant_config.get("tools") is not None: + assistant_update_kwargs["tools"] = assistant_config["tools"] + + if assistant_config.get("instructions") is not None: + assistant_update_kwargs["instructions"] = assistant_config["instructions"] + + if gpt_assistant_api_version == "v2": + if assistant_config.get("tool_resources") is not None: + assistant_update_kwargs["tool_resources"] = assistant_config["tool_resources"] + else: + if assistant_config.get("file_ids") is not None: + assistant_update_kwargs["file_ids"] = assistant_config["file_ids"] + + return client.beta.assistants.update(assistant_id=assistant_id, **assistant_update_kwargs) diff --git a/autogen/retrieve_utils.py b/autogen/retrieve_utils.py index c3e50c7a96f..9393903ec86 100644 --- a/autogen/retrieve_utils.py +++ b/autogen/retrieve_utils.py @@ -1,18 +1,25 @@ -from typing import List, Union, Callable +import glob +import hashlib import os -import requests +import re +from typing import Callable, List, Tuple, Union from urllib.parse import urlparse -import glob + import chromadb +import markdownify +import requests +from bs4 import BeautifulSoup if chromadb.__version__ < "0.4.15": from chromadb.api import API else: from chromadb.api import ClientAPI as API -from chromadb.api.types import QueryResult -import chromadb.utils.embedding_functions as ef import logging + +import chromadb.utils.embedding_functions as ef import pypdf +from chromadb.api.types import QueryResult + from autogen.token_count_utils import count_token try: @@ -58,6 +65,7 @@ TEXT_FORMATS += UNSTRUCTURED_FORMATS TEXT_FORMATS = list(set(TEXT_FORMATS)) VALID_CHUNK_MODES = frozenset({"one_line", "multi_lines"}) +RAG_MINIMUM_MESSAGE_LENGTH = int(os.environ.get("RAG_MINIMUM_MESSAGE_LENGTH", 5)) def split_text_to_chunks( @@ -65,22 +73,27 @@ def split_text_to_chunks( max_tokens: int = 4000, chunk_mode: str = "multi_lines", must_break_at_empty_line: bool = True, - overlap: int = 10, + overlap: int = 0, # number of overlapping lines ): """Split a long text into chunks of max_tokens.""" if chunk_mode not in VALID_CHUNK_MODES: raise AssertionError if chunk_mode == "one_line": must_break_at_empty_line = False + overlap = 0 chunks = [] lines = text.split("\n") + num_lines = len(lines) + if num_lines < 3 and must_break_at_empty_line: + logger.warning("The input text has less than 3 lines. Set `must_break_at_empty_line` to `False`") + must_break_at_empty_line = False lines_tokens = [count_token(line) for line in lines] sum_tokens = sum(lines_tokens) while sum_tokens > max_tokens: if chunk_mode == "one_line": estimated_line_cut = 2 else: - estimated_line_cut = int(max_tokens / sum_tokens * len(lines)) + 1 + estimated_line_cut = max(int(max_tokens / sum_tokens * len(lines)), 2) cnt = 0 prev = "" for cnt in reversed(range(estimated_line_cut)): @@ -94,19 +107,25 @@ def split_text_to_chunks( f"max_tokens is too small to fit a single line of text. Breaking this line:\n\t{lines[0][:100]} ..." ) if not must_break_at_empty_line: - split_len = int(max_tokens / lines_tokens[0] * 0.9 * len(lines[0])) + split_len = max( + int(max_tokens / (lines_tokens[0] * 0.9 * len(lines[0]) + 0.1)), RAG_MINIMUM_MESSAGE_LENGTH + ) prev = lines[0][:split_len] lines[0] = lines[0][split_len:] lines_tokens[0] = count_token(lines[0]) else: logger.warning("Failed to split docs with must_break_at_empty_line being True, set to False.") must_break_at_empty_line = False - chunks.append(prev) if len(prev) > 10 else None # don't add chunks less than 10 characters - lines = lines[cnt:] - lines_tokens = lines_tokens[cnt:] + ( + chunks.append(prev) if len(prev) >= RAG_MINIMUM_MESSAGE_LENGTH else None + ) # don't add chunks less than RAG_MINIMUM_MESSAGE_LENGTH characters + lines = lines[cnt - overlap if cnt > overlap else cnt :] + lines_tokens = lines_tokens[cnt - overlap if cnt > overlap else cnt :] sum_tokens = sum(lines_tokens) - text_to_chunk = "\n".join(lines) - chunks.append(text_to_chunk) if len(text_to_chunk) > 10 else None # don't add chunks less than 10 characters + text_to_chunk = "\n".join(lines).strip() + ( + chunks.append(text_to_chunk) if len(text_to_chunk) >= RAG_MINIMUM_MESSAGE_LENGTH else None + ) # don't add chunks less than RAG_MINIMUM_MESSAGE_LENGTH characters return chunks @@ -138,12 +157,18 @@ def split_files_to_chunks( chunk_mode: str = "multi_lines", must_break_at_empty_line: bool = True, custom_text_split_function: Callable = None, -): +) -> Tuple[List[str], List[dict]]: """Split a list of files into chunks of max_tokens.""" chunks = [] + sources = [] for file in files: + if isinstance(file, tuple): + url = file[1] + file = file[0] + else: + url = None _, file_extension = os.path.splitext(file) file_extension = file_extension.lower() @@ -161,11 +186,13 @@ def split_files_to_chunks( continue # Skip to the next file if no text is available if custom_text_split_function is not None: - chunks += custom_text_split_function(text) + tmp_chunks = custom_text_split_function(text) else: - chunks += split_text_to_chunks(text, max_tokens, chunk_mode, must_break_at_empty_line) + tmp_chunks = split_text_to_chunks(text, max_tokens, chunk_mode, must_break_at_empty_line) + chunks += tmp_chunks + sources += [{"source": url if url else file}] * len(tmp_chunks) - return chunks + return chunks, sources def get_files_from_dir(dir_path: Union[str, List[str]], types: list = TEXT_FORMATS, recursive: bool = True): @@ -182,7 +209,9 @@ def get_files_from_dir(dir_path: Union[str, List[str]], types: list = TEXT_FORMA if os.path.isfile(item): files.append(item) elif is_url(item): - files.append(get_file_from_url(item)) + filepath = get_file_from_url(item) + if filepath: + files.append(filepath) elif os.path.exists(item): try: files.extend(get_files_from_dir(item, types, recursive)) @@ -198,7 +227,11 @@ def get_files_from_dir(dir_path: Union[str, List[str]], types: list = TEXT_FORMA # If the path is a url, download it and return the downloaded file if is_url(dir_path): - return [get_file_from_url(dir_path)] + filepath = get_file_from_url(dir_path) + if filepath: + return [filepath] + else: + return [] if os.path.exists(dir_path): for type in types: @@ -212,19 +245,81 @@ def get_files_from_dir(dir_path: Union[str, List[str]], types: list = TEXT_FORMA return files -def get_file_from_url(url: str, save_path: str = None): +def parse_html_to_markdown(html: str, url: str = None) -> str: + """Parse HTML to markdown.""" + soup = BeautifulSoup(html, "html.parser") + title = soup.title.string + # Remove javascript and style blocks + for script in soup(["script", "style"]): + script.extract() + + # Convert to markdown -- Wikipedia gets special attention to get a clean version of the page + if isinstance(url, str) and url.startswith("https://en.wikipedia.org/"): + body_elm = soup.find("div", {"id": "mw-content-text"}) + title_elm = soup.find("span", {"class": "mw-page-title-main"}) + + if body_elm: + # What's the title + main_title = soup.title.string + if title_elm and len(title_elm) > 0: + main_title = title_elm.string + webpage_text = "# " + main_title + "\n\n" + markdownify.MarkdownConverter().convert_soup(body_elm) + else: + webpage_text = markdownify.MarkdownConverter().convert_soup(soup) + else: + webpage_text = markdownify.MarkdownConverter().convert_soup(soup) + + # Convert newlines + webpage_text = re.sub(r"\r\n", "\n", webpage_text) + webpage_text = re.sub(r"\n{2,}", "\n\n", webpage_text).strip() + webpage_text = "# " + title + "\n\n" + webpage_text + return webpage_text + + +def _generate_file_name_from_url(url: str, max_length=255) -> str: + url_bytes = url.encode("utf-8") + hash = hashlib.blake2b(url_bytes).hexdigest() + parsed_url = urlparse(url) + file_name = os.path.basename(url) + file_name = f"{parsed_url.netloc}_{file_name}_{hash[:min(8, max_length-len(parsed_url.netloc)-len(file_name)-1)]}" + return file_name + + +def get_file_from_url(url: str, save_path: str = None) -> Tuple[str, str]: """Download a file from a URL.""" if save_path is None: - os.makedirs("/tmp/chromadb", exist_ok=True) - save_path = os.path.join("/tmp/chromadb", os.path.basename(url)) + save_path = "tmp/chromadb" + os.makedirs(save_path, exist_ok=True) + if os.path.isdir(save_path): + filename = _generate_file_name_from_url(url) + save_path = os.path.join(save_path, filename) else: os.makedirs(os.path.dirname(save_path), exist_ok=True) - with requests.get(url, stream=True) as r: - r.raise_for_status() + + custom_headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36", + } + try: + response = requests.get(url, stream=True, headers=custom_headers, timeout=30) + response.raise_for_status() + except requests.exceptions.RequestException as e: + logger.warning(f"Failed to download {url}, {e}") + return None + + content_type = response.headers.get("content-type", "") + if "text/html" in content_type: + # Get the content of the response + html = "" + for chunk in response.iter_content(chunk_size=8192, decode_unicode=True): + html += chunk + text = parse_html_to_markdown(html, url) + with open(save_path, "w", encoding="utf-8") as f: + f.write(text) + else: with open(save_path, "wb") as f: - for chunk in r.iter_content(chunk_size=8192): + for chunk in response.iter_content(chunk_size=8192): f.write(chunk) - return save_path + return save_path, url def is_url(string: str): @@ -240,7 +335,7 @@ def create_vector_db_from_dir( dir_path: Union[str, List[str]], max_tokens: int = 4000, client: API = None, - db_path: str = "/tmp/chromadb.db", + db_path: str = "tmp/chromadb.db", collection_name: str = "all-my-documents", get_or_create: bool = False, chunk_mode: str = "multi_lines", @@ -260,7 +355,7 @@ def create_vector_db_from_dir( dir_path (Union[str, List[str]]): the path to the directory, file, url or a list of them. max_tokens (Optional, int): the maximum number of tokens per chunk. Default is 4000. client (Optional, API): the chromadb client. Default is None. - db_path (Optional, str): the path to the chromadb. Default is "/tmp/chromadb.db". + db_path (Optional, str): the path to the chromadb. Default is "tmp/chromadb.db". The default was `/tmp/chromadb.db` for version <=0.2.24. collection_name (Optional, str): the name of the collection. Default is "all-my-documents". get_or_create (Optional, bool): Whether to get or create the collection. Default is False. If True, the collection will be returned if it already exists. Will raise ValueError if the collection already exists and get_or_create is False. @@ -304,12 +399,12 @@ def create_vector_db_from_dir( length = len(collection.get()["ids"]) if custom_text_split_function is not None: - chunks = split_files_to_chunks( + chunks, sources = split_files_to_chunks( get_files_from_dir(dir_path, custom_text_types, recursive), custom_text_split_function=custom_text_split_function, ) else: - chunks = split_files_to_chunks( + chunks, sources = split_files_to_chunks( get_files_from_dir(dir_path, custom_text_types, recursive), max_tokens, chunk_mode, @@ -322,6 +417,7 @@ def create_vector_db_from_dir( collection.upsert( documents=chunks[i:end_idx], ids=[f"doc_{j+length}" for j in range(i, end_idx)], # unique for each doc + metadatas=sources[i:end_idx], ) except ValueError as e: logger.warning(f"{e}") @@ -332,7 +428,7 @@ def query_vector_db( query_texts: List[str], n_results: int = 10, client: API = None, - db_path: str = "/tmp/chromadb.db", + db_path: str = "tmp/chromadb.db", collection_name: str = "all-my-documents", search_string: str = "", embedding_model: str = "all-MiniLM-L6-v2", @@ -345,7 +441,7 @@ def query_vector_db( query_texts (List[str]): the list of strings which will be used to query the vector db. n_results (Optional, int): the number of results to return. Default is 10. client (Optional, API): the chromadb compatible client. Default is None, a chromadb client will be used. - db_path (Optional, str): the path to the vector db. Default is "/tmp/chromadb.db". + db_path (Optional, str): the path to the vector db. Default is "tmp/chromadb.db". The default was `/tmp/chromadb.db` for version <=0.2.24. collection_name (Optional, str): the name of the collection. Default is "all-my-documents". search_string (Optional, str): the search string. Only docs that contain an exact match of this string will be retrieved. Default is "". embedding_model (Optional, str): the embedding model to use. Default is "all-MiniLM-L6-v2". Will be ignored if diff --git a/autogen/runtime_logging.py b/autogen/runtime_logging.py index 94d7460cd30..d848ca3645e 100644 --- a/autogen/runtime_logging.py +++ b/autogen/runtime_logging.py @@ -1,18 +1,18 @@ from __future__ import annotations -from autogen.logger.logger_factory import LoggerFactory -from autogen.logger.base_logger import LLMConfig - import logging import sqlite3 -from typing import Any, Dict, List, Optional, TYPE_CHECKING, Union import uuid +from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Union -from openai import OpenAI, AzureOpenAI +from openai import AzureOpenAI, OpenAI from openai.types.chat import ChatCompletion +from autogen.logger.base_logger import BaseLogger, LLMConfig +from autogen.logger.logger_factory import LoggerFactory + if TYPE_CHECKING: - from autogen import ConversableAgent, OpenAIWrapper + from autogen import Agent, ConversableAgent, OpenAIWrapper logger = logging.getLogger(__name__) @@ -20,11 +20,27 @@ is_logging = False -def start(logger_type: str = "sqlite", config: Optional[Dict[str, Any]] = None) -> str: +def start( + logger: Optional[BaseLogger] = None, + logger_type: Literal["sqlite", "file"] = "sqlite", + config: Optional[Dict[str, Any]] = None, +) -> str: + """ + Start logging for the runtime. + Args: + logger (BaseLogger): A logger instance + logger_type (str): The type of logger to use (default: sqlite) + config (dict): Configuration for the logger + Returns: + session_id (str(uuid.uuid4)): a unique id for the logging session + """ global autogen_logger global is_logging - autogen_logger = LoggerFactory.get_logger(logger_type=logger_type, config=config) + if logger: + autogen_logger = logger + else: + autogen_logger = LoggerFactory.get_logger(logger_type=logger_type, config=config) try: session_id = autogen_logger.start() @@ -62,6 +78,14 @@ def log_new_agent(agent: ConversableAgent, init_args: Dict[str, Any]) -> None: autogen_logger.log_new_agent(agent, init_args) +def log_event(source: Union[str, Agent], name: str, **kwargs: Dict[str, Any]) -> None: + if autogen_logger is None: + logger.error("[runtime logging] log_event: autogen logger is None") + return + + autogen_logger.log_event(source, name, **kwargs) + + def log_new_wrapper(wrapper: OpenAIWrapper, init_args: Dict[str, Union[LLMConfig, List[LLMConfig]]]) -> None: if autogen_logger is None: logger.error("[runtime logging] log_new_wrapper: autogen logger is None") diff --git a/autogen/token_count_utils.py b/autogen/token_count_utils.py index 84fe147fd8e..589d7b404a7 100644 --- a/autogen/token_count_utils.py +++ b/autogen/token_count_utils.py @@ -1,9 +1,9 @@ -from typing import List, Union, Dict -import logging import json -import tiktoken +import logging import re +from typing import Dict, List, Union +import tiktoken logger = logging.getLogger(__name__) @@ -14,7 +14,8 @@ def get_max_token_limit(model: str = "gpt-3.5-turbo-0613") -> int: model = re.sub(r"^gpt4", "gpt-4", model) max_token_limit = { - "gpt-3.5-turbo": 4096, + "gpt-3.5-turbo": 16385, + "gpt-3.5-turbo-0125": 16385, "gpt-3.5-turbo-0301": 4096, "gpt-3.5-turbo-0613": 4096, "gpt-3.5-turbo-instruct": 4096, @@ -22,6 +23,8 @@ def get_max_token_limit(model: str = "gpt-3.5-turbo-0613") -> int: "gpt-3.5-turbo-16k-0613": 16385, "gpt-3.5-turbo-1106": 16385, "gpt-4": 8192, + "gpt-4-turbo": 128000, + "gpt-4-turbo-2024-04-09": 128000, "gpt-4-32k": 32768, "gpt-4-32k-0314": 32768, # deprecate in Sep "gpt-4-0314": 8192, # deprecate in Sep @@ -66,7 +69,7 @@ def count_token(input: Union[str, List, Dict], model: str = "gpt-3.5-turbo-0613" elif isinstance(input, list) or isinstance(input, dict): return _num_token_from_messages(input, model=model) else: - raise ValueError("input must be str, list or dict") + raise ValueError(f"input must be str, list or dict, but we got {type(input)}") def _num_token_from_text(text: str, model: str = "gpt-3.5-turbo-0613"): @@ -111,6 +114,9 @@ def _num_token_from_messages(messages: Union[List, Dict], model="gpt-3.5-turbo-0 elif "gpt-4" in model: logger.info("gpt-4 may update over time. Returning num tokens assuming gpt-4-0613.") return _num_token_from_messages(messages, model="gpt-4-0613") + elif "gemini" in model: + logger.info("Gemini is not supported in tiktoken. Returning num tokens assuming gpt-4-0613.") + return _num_token_from_messages(messages, model="gpt-4-0613") else: raise NotImplementedError( f"""_num_token_from_messages() is not implemented for model {model}. See https://github.com/openai/openai-python/blob/main/chatml.md for information on how messages are converted to tokens.""" diff --git a/autogen/version.py b/autogen/version.py index 198d6db6273..be2d7c2ffe3 100644 --- a/autogen/version.py +++ b/autogen/version.py @@ -1 +1 @@ -__version__ = "0.2.20" +__version__ = "0.2.27" diff --git a/dotnet/.config/dotnet-tools.json b/dotnet/.config/dotnet-tools.json index 5b341cff736..6b2517ea2c6 100644 --- a/dotnet/.config/dotnet-tools.json +++ b/dotnet/.config/dotnet-tools.json @@ -1,12 +1,18 @@ { - "version": 1, - "isRoot": true, - "tools": { - "dotnet-repl": { - "version": "0.1.205", - "commands": [ - "dotnet-repl" - ] - } + "version": 1, + "isRoot": true, + "tools": { + "dotnet-repl": { + "version": "0.1.205", + "commands": [ + "dotnet-repl" + ] + }, + "docfx": { + "version": "2.67.5", + "commands": [ + "docfx" + ] } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/dotnet/.editorconfig b/dotnet/.editorconfig new file mode 100644 index 00000000000..4da1adc5de6 --- /dev/null +++ b/dotnet/.editorconfig @@ -0,0 +1,178 @@ +ο»Ώ# EditorConfig is awesome:http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Don't use tabs for indentation. +[*] +indent_style = space +# (Please don't specify an indent_size here; that has too many unintended consequences.) + +# Code files +[*.{cs,csx,vb,vbx}] +indent_size = 4 +insert_final_newline = true +charset = utf-8-bom + +[*.xaml] +indent_size = 4 + +[*.ps1] +indent_size = 2 + +# Xml project files +[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] +indent_size = 2 + +# Xml config files +[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] +indent_size = 2 + +# JSON files +[*.json] +indent_size = 2 + +[*.groovy] +indent_size = 2 + +# Dotnet code style settings: +[*.{cs,vb}] +# Sort using and Import directives with System.* appearing first +dotnet_sort_system_directives_first = true +dotnet_style_require_accessibility_modifiers = always:warning + +# No blank line between System.* and Microsoft.* +dotnet_separate_import_directive_groups = false + +# Suggest more modern language features when available +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_coalesce_expression = true:error +dotnet_style_null_propagation = true:error +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_conditional_expression_over_return = false +dotnet_style_prefer_conditional_expression_over_assignment = false +dotnet_style_prefer_auto_properties = false + +# Use language keywords instead of framework type names for type references +dotnet_style_predefined_type_for_locals_parameters_members = true:error +dotnet_style_predefined_type_for_member_access = true:error + +# Prefer read-only on fields +dotnet_style_readonly_field = false + +# CSharp code style settings: +[*.cs] + +# Prefer "var" only when the type is apparent +csharp_style_var_for_built_in_types = false:suggestion +csharp_style_var_when_type_is_apparent = true:suggestion +csharp_style_var_elsewhere = false:suggestion + +# Prefer method-like constructs to have a block body +csharp_style_expression_bodied_methods = false:none +csharp_style_expression_bodied_constructors = false:none +csharp_style_expression_bodied_operators = false:none + +# Prefer property-like constructs to have an expression-body +csharp_style_expression_bodied_properties = true:none +csharp_style_expression_bodied_indexers = true:none +csharp_style_expression_bodied_accessors = true:none + +# Use block body for local functions +csharp_style_expression_bodied_local_functions = when_on_single_line:silent + +# Suggest more modern language features when available +csharp_style_pattern_matching_over_is_with_cast_check = true:error +csharp_style_pattern_matching_over_as_with_null_check = true:error +csharp_style_inlined_variable_declaration = true:error +csharp_style_throw_expression = true:suggestion +csharp_style_conditional_delegate_call = true:suggestion +csharp_style_deconstructed_variable_declaration = true:suggestion + +# Newline settings +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true + +# Identation options +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_switch_labels = true +csharp_indent_labels = no_change +csharp_indent_block_contents = true +csharp_indent_braces = false + +# Spacing options +csharp_space_after_cast = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false +csharp_space_between_empty_square_brackets = false +csharp_space_before_open_square_brackets = false +csharp_space_around_declaration_statements = false +csharp_space_around_binary_operators = before_and_after +csharp_space_after_cast = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_before_dot = false +csharp_space_after_dot = false +csharp_space_before_comma = false +csharp_space_after_comma = true +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_semicolon_in_for_statement = true + +# Wrapping +csharp_preserve_single_line_statements = true +csharp_preserve_single_line_blocks = true + +# Code block +csharp_prefer_braces = false:none + +# Using statements +csharp_using_directive_placement = outside_namespace:error + +# Modifier settings +csharp_prefer_static_local_function = true:warning +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:warning + +# Header template +file_header_template = Copyright (c) Microsoft Corporation. All rights reserved.\n{fileName} +dotnet_diagnostic.IDE0073.severity = error + +# enable format error +dotnet_diagnostic.IDE0055.severity = error + +# IDE0035: Remove unreachable code +dotnet_diagnostic.IDE0035.severity = error + +# IDE0005: Remove unncecessary usings +dotnet_diagnostic.CS8019.severity = error +dotnet_diagnostic.IDE0005.severity = error + +# IDE0069: Remove unused local variable +dotnet_diagnostic.IDE0069.severity = error + +# disable CS1573: Parameter has no matching param tag in the XML comment for +dotnet_diagnostic.CS1573.severity = none + +# disable CS1570: XML comment has badly formed XML +dotnet_diagnostic.CS1570.severity = none + +# disable check for generated code +[*.generated.cs] +generated_code = true \ No newline at end of file diff --git a/dotnet/.gitignore b/dotnet/.gitignore new file mode 100644 index 00000000000..65e7ba678dd --- /dev/null +++ b/dotnet/.gitignore @@ -0,0 +1,30 @@ +# gitignore file for C#/VS + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +build/ +bld/ +[Bb]in/ +[Oo]bj/ + +# vs cache +.vs/ + +# vs code cache +.vscode/ + +# Properties +Properties/ + +artifacts/ +output/ + +*.binlog + +# JetBrains Rider +.idea/ \ No newline at end of file diff --git a/dotnet/AutoGen.sln b/dotnet/AutoGen.sln new file mode 100644 index 00000000000..b29e5e21e95 --- /dev/null +++ b/dotnet/AutoGen.sln @@ -0,0 +1,145 @@ +ο»Ώ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.8.34322.80 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen", "src\AutoGen\AutoGen.csproj", "{B2B27ACB-AA50-4FED-A06C-3AD6B4218188}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{18BF8DD7-0585-48BF-8F97-AD333080CE06}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{F823671B-3ECA-4AE6-86DA-25E920D3FE64}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.Tests", "test\AutoGen.Tests\AutoGen.Tests.csproj", "{FDD99AEC-4C57-4020-B23F-650612856102}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.SourceGenerator", "src\AutoGen.SourceGenerator\AutoGen.SourceGenerator.csproj", "{3FFD14E3-D6BC-4EA7-97A2-D21733060FD6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.SourceGenerator.Tests", "test\AutoGen.SourceGenerator.Tests\AutoGen.SourceGenerator.Tests.csproj", "{05A2FAD8-03B0-4B2F-82AF-2F6BF0F050E5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.BasicSample", "sample\AutoGen.BasicSamples\AutoGen.BasicSample.csproj", "{7EBF916A-A7B1-4B74-AF10-D705B7A18F58}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sample", "sample", "{FBFEAD1F-29EB-4D99-A672-0CD8473E10B9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.DotnetInteractive", "src\AutoGen.DotnetInteractive\AutoGen.DotnetInteractive.csproj", "{B61D8008-7FB7-4C0E-8044-3A74AA63A596}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.LMStudio", "src\AutoGen.LMStudio\AutoGen.LMStudio.csproj", "{F98BDA9B-8657-4BA8-9B03-BAEA454CAE60}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.SemanticKernel", "src\AutoGen.SemanticKernel\AutoGen.SemanticKernel.csproj", "{45D6FC80-36F3-4967-9663-E20B63824621}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.Core", "src\AutoGen.Core\AutoGen.Core.csproj", "{D58D43D1-0617-4A3D-9932-C773E6398535}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.OpenAI", "src\AutoGen.OpenAI\AutoGen.OpenAI.csproj", "{63445BB7-DBB9-4AEF-9D6F-98BBE75EE1EC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.Mistral", "src\AutoGen.Mistral\AutoGen.Mistral.csproj", "{6585D1A4-3D97-4D76-A688-1933B61AEB19}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.Mistral.Tests", "test\AutoGen.Mistral.Tests\AutoGen.Mistral.Tests.csproj", "{15441693-3659-4868-B6C1-B106F52FF3BA}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.SemanticKernel.Tests", "test\AutoGen.SemanticKernel.Tests\AutoGen.SemanticKernel.Tests.csproj", "{1DFABC4A-8458-4875-8DCB-59F3802DAC65}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoGen.OpenAI.Tests", "test\AutoGen.OpenAI.Tests\AutoGen.OpenAI.Tests.csproj", "{D36A85F9-C172-487D-8192-6BFE5D05B4A7}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoGen.DotnetInteractive.Tests", "test\AutoGen.DotnetInteractive.Tests\AutoGen.DotnetInteractive.Tests.csproj", "{B61388CA-DC73-4B7F-A7B2-7B9A86C9229E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Autogen.Ollama", "src\Autogen.Ollama\Autogen.Ollama.csproj", "{A4EFA175-44CC-44A9-B93E-1C7B6FAC38F1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Autogen.Ollama.Tests", "test\Autogen.Ollama.Tests\Autogen.Ollama.Tests.csproj", "{C24FDE63-952D-4F8E-A807-AF31D43AD675}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B2B27ACB-AA50-4FED-A06C-3AD6B4218188}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B2B27ACB-AA50-4FED-A06C-3AD6B4218188}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B2B27ACB-AA50-4FED-A06C-3AD6B4218188}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B2B27ACB-AA50-4FED-A06C-3AD6B4218188}.Release|Any CPU.Build.0 = Release|Any CPU + {FDD99AEC-4C57-4020-B23F-650612856102}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FDD99AEC-4C57-4020-B23F-650612856102}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FDD99AEC-4C57-4020-B23F-650612856102}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FDD99AEC-4C57-4020-B23F-650612856102}.Release|Any CPU.Build.0 = Release|Any CPU + {3FFD14E3-D6BC-4EA7-97A2-D21733060FD6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3FFD14E3-D6BC-4EA7-97A2-D21733060FD6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3FFD14E3-D6BC-4EA7-97A2-D21733060FD6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3FFD14E3-D6BC-4EA7-97A2-D21733060FD6}.Release|Any CPU.Build.0 = Release|Any CPU + {05A2FAD8-03B0-4B2F-82AF-2F6BF0F050E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {05A2FAD8-03B0-4B2F-82AF-2F6BF0F050E5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {05A2FAD8-03B0-4B2F-82AF-2F6BF0F050E5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {05A2FAD8-03B0-4B2F-82AF-2F6BF0F050E5}.Release|Any CPU.Build.0 = Release|Any CPU + {7EBF916A-A7B1-4B74-AF10-D705B7A18F58}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7EBF916A-A7B1-4B74-AF10-D705B7A18F58}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7EBF916A-A7B1-4B74-AF10-D705B7A18F58}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7EBF916A-A7B1-4B74-AF10-D705B7A18F58}.Release|Any CPU.Build.0 = Release|Any CPU + {B61D8008-7FB7-4C0E-8044-3A74AA63A596}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B61D8008-7FB7-4C0E-8044-3A74AA63A596}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B61D8008-7FB7-4C0E-8044-3A74AA63A596}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B61D8008-7FB7-4C0E-8044-3A74AA63A596}.Release|Any CPU.Build.0 = Release|Any CPU + {F98BDA9B-8657-4BA8-9B03-BAEA454CAE60}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F98BDA9B-8657-4BA8-9B03-BAEA454CAE60}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F98BDA9B-8657-4BA8-9B03-BAEA454CAE60}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F98BDA9B-8657-4BA8-9B03-BAEA454CAE60}.Release|Any CPU.Build.0 = Release|Any CPU + {45D6FC80-36F3-4967-9663-E20B63824621}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {45D6FC80-36F3-4967-9663-E20B63824621}.Debug|Any CPU.Build.0 = Debug|Any CPU + {45D6FC80-36F3-4967-9663-E20B63824621}.Release|Any CPU.ActiveCfg = Release|Any CPU + {45D6FC80-36F3-4967-9663-E20B63824621}.Release|Any CPU.Build.0 = Release|Any CPU + {D58D43D1-0617-4A3D-9932-C773E6398535}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D58D43D1-0617-4A3D-9932-C773E6398535}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D58D43D1-0617-4A3D-9932-C773E6398535}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D58D43D1-0617-4A3D-9932-C773E6398535}.Release|Any CPU.Build.0 = Release|Any CPU + {63445BB7-DBB9-4AEF-9D6F-98BBE75EE1EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {63445BB7-DBB9-4AEF-9D6F-98BBE75EE1EC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {63445BB7-DBB9-4AEF-9D6F-98BBE75EE1EC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {63445BB7-DBB9-4AEF-9D6F-98BBE75EE1EC}.Release|Any CPU.Build.0 = Release|Any CPU + {6585D1A4-3D97-4D76-A688-1933B61AEB19}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6585D1A4-3D97-4D76-A688-1933B61AEB19}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6585D1A4-3D97-4D76-A688-1933B61AEB19}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6585D1A4-3D97-4D76-A688-1933B61AEB19}.Release|Any CPU.Build.0 = Release|Any CPU + {15441693-3659-4868-B6C1-B106F52FF3BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {15441693-3659-4868-B6C1-B106F52FF3BA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {15441693-3659-4868-B6C1-B106F52FF3BA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {15441693-3659-4868-B6C1-B106F52FF3BA}.Release|Any CPU.Build.0 = Release|Any CPU + {A4EFA175-44CC-44A9-B93E-1C7B6FAC38F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A4EFA175-44CC-44A9-B93E-1C7B6FAC38F1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A4EFA175-44CC-44A9-B93E-1C7B6FAC38F1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A4EFA175-44CC-44A9-B93E-1C7B6FAC38F1}.Release|Any CPU.Build.0 = Release|Any CPU + {C24FDE63-952D-4F8E-A807-AF31D43AD675}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C24FDE63-952D-4F8E-A807-AF31D43AD675}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C24FDE63-952D-4F8E-A807-AF31D43AD675}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C24FDE63-952D-4F8E-A807-AF31D43AD675}.Release|Any CPU.Build.0 = Release|Any CPU + {1DFABC4A-8458-4875-8DCB-59F3802DAC65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1DFABC4A-8458-4875-8DCB-59F3802DAC65}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1DFABC4A-8458-4875-8DCB-59F3802DAC65}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1DFABC4A-8458-4875-8DCB-59F3802DAC65}.Release|Any CPU.Build.0 = Release|Any CPU + {D36A85F9-C172-487D-8192-6BFE5D05B4A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D36A85F9-C172-487D-8192-6BFE5D05B4A7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D36A85F9-C172-487D-8192-6BFE5D05B4A7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D36A85F9-C172-487D-8192-6BFE5D05B4A7}.Release|Any CPU.Build.0 = Release|Any CPU + {B61388CA-DC73-4B7F-A7B2-7B9A86C9229E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B61388CA-DC73-4B7F-A7B2-7B9A86C9229E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B61388CA-DC73-4B7F-A7B2-7B9A86C9229E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B61388CA-DC73-4B7F-A7B2-7B9A86C9229E}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {B2B27ACB-AA50-4FED-A06C-3AD6B4218188} = {18BF8DD7-0585-48BF-8F97-AD333080CE06} + {FDD99AEC-4C57-4020-B23F-650612856102} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64} + {3FFD14E3-D6BC-4EA7-97A2-D21733060FD6} = {18BF8DD7-0585-48BF-8F97-AD333080CE06} + {05A2FAD8-03B0-4B2F-82AF-2F6BF0F050E5} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64} + {7EBF916A-A7B1-4B74-AF10-D705B7A18F58} = {FBFEAD1F-29EB-4D99-A672-0CD8473E10B9} + {B61D8008-7FB7-4C0E-8044-3A74AA63A596} = {18BF8DD7-0585-48BF-8F97-AD333080CE06} + {F98BDA9B-8657-4BA8-9B03-BAEA454CAE60} = {18BF8DD7-0585-48BF-8F97-AD333080CE06} + {45D6FC80-36F3-4967-9663-E20B63824621} = {18BF8DD7-0585-48BF-8F97-AD333080CE06} + {D58D43D1-0617-4A3D-9932-C773E6398535} = {18BF8DD7-0585-48BF-8F97-AD333080CE06} + {63445BB7-DBB9-4AEF-9D6F-98BBE75EE1EC} = {18BF8DD7-0585-48BF-8F97-AD333080CE06} + {6585D1A4-3D97-4D76-A688-1933B61AEB19} = {18BF8DD7-0585-48BF-8F97-AD333080CE06} + {15441693-3659-4868-B6C1-B106F52FF3BA} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64} + {A4EFA175-44CC-44A9-B93E-1C7B6FAC38F1} = {18BF8DD7-0585-48BF-8F97-AD333080CE06} + {C24FDE63-952D-4F8E-A807-AF31D43AD675} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64} + {1DFABC4A-8458-4875-8DCB-59F3802DAC65} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64} + {D36A85F9-C172-487D-8192-6BFE5D05B4A7} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64} + {B61388CA-DC73-4B7F-A7B2-7B9A86C9229E} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {93384647-528D-46C8-922C-8DB36A382F0B} + EndGlobalSection +EndGlobal diff --git a/dotnet/Directory.Build.props b/dotnet/Directory.Build.props new file mode 100644 index 00000000000..aeb667438e2 --- /dev/null +++ b/dotnet/Directory.Build.props @@ -0,0 +1,24 @@ + + + + + + + net8.0 + preview + enable + True + $(MSBuildThisFileDirectory)eng/opensource.snk + 0024000004800000940000000602000000240000525341310004000001000100f1d038d0b85ae392ad72011df91e9343b0b5df1bb8080aa21b9424362d696919e0e9ac3a8bca24e283e10f7a569c6f443e1d4e3ebc84377c87ca5caa562e80f9932bf5ea91b7862b538e13b8ba91c7565cf0e8dfeccfea9c805ae3bda044170ecc7fc6f147aeeac422dd96aeb9eb1f5a5882aa650efe2958f2f8107d2038f2ab + CS1998;CS1591 + $(NoWarn);$(CSNoWarn);NU5104 + true + false + true + true + + + + $(MSBuildThisFileDirectory) + + diff --git a/dotnet/NuGet.config b/dotnet/NuGet.config new file mode 100644 index 00000000000..1d0cf4c2bc7 --- /dev/null +++ b/dotnet/NuGet.config @@ -0,0 +1,8 @@ +ο»Ώ + + + + + + + \ No newline at end of file diff --git a/dotnet/README.md b/dotnet/README.md new file mode 100644 index 00000000000..5b0803b6e11 --- /dev/null +++ b/dotnet/README.md @@ -0,0 +1,103 @@ +### AutoGen for .NET + +[![dotnet-ci](https://github.com/microsoft/autogen/actions/workflows/dotnet-build.yml/badge.svg)](https://github.com/microsoft/autogen/actions/workflows/dotnet-build.yml) +[![NuGet version](https://badge.fury.io/nu/AutoGen.Core.svg)](https://badge.fury.io/nu/AutoGen.Core) + +> [!NOTE] +> Nightly build is available at: +> - ![Static Badge](https://img.shields.io/badge/public-blue?style=flat) ![Static Badge](https://img.shields.io/badge/nightly-yellow?style=flat) ![Static Badge](https://img.shields.io/badge/github-grey?style=flat): https://nuget.pkg.github.com/microsoft/index.json +> - ![Static Badge](https://img.shields.io/badge/public-blue?style=flat) ![Static Badge](https://img.shields.io/badge/nightly-yellow?style=flat) ![Static Badge](https://img.shields.io/badge/myget-grey?style=flat): https://www.myget.org/F/agentchat/api/v3/index.json +> - ![Static Badge](https://img.shields.io/badge/internal-blue?style=flat) ![Static Badge](https://img.shields.io/badge/nightly-yellow?style=flat) ![Static Badge](https://img.shields.io/badge/azure_devops-grey?style=flat) : https://devdiv.pkgs.visualstudio.com/DevDiv/_packaging/AutoGen/nuget/v3/index.json + + +Firstly, following the [installation guide](./website/articles/Installation.md) to install AutoGen packages. + +Then you can start with the following code snippet to create a conversable agent and chat with it. + +```csharp +using AutoGen; +using AutoGen.OpenAI; + +var openAIKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new Exception("Please set OPENAI_API_KEY environment variable."); +var gpt35Config = new OpenAIConfig(openAIKey, "gpt-3.5-turbo"); + +var assistantAgent = new AssistantAgent( + name: "assistant", + systemMessage: "You are an assistant that help user to do some tasks.", + llmConfig: new ConversableAgentConfig + { + Temperature = 0, + ConfigList = [gpt35Config], + }) + .RegisterPrintMessage(); // register a hook to print message nicely to console + +// set human input mode to ALWAYS so that user always provide input +var userProxyAgent = new UserProxyAgent( + name: "user", + humanInputMode: ConversableAgent.HumanInputMode.ALWAYS) + .RegisterPrintMessage(); + +// start the conversation +await userProxyAgent.InitiateChatAsync( + receiver: assistantAgent, + message: "Hey assistant, please do me a favor.", + maxRound: 10); +``` + +#### Samples +You can find more examples under the [sample project](https://github.com/microsoft/autogen/tree/dotnet/dotnet/sample/AutoGen.BasicSamples). + +#### Functionality +- ConversableAgent + - [x] function call + - [x] code execution (dotnet only, powered by [`dotnet-interactive`](https://github.com/dotnet/interactive)) + +- Agent communication + - [x] Two-agent chat + - [x] Group chat + +- [ ] Enhanced LLM Inferences + +- Exclusive for dotnet + - [x] Source generator for type-safe function definition generation + +#### Update log +##### Update on 0.0.11 (2024-03-26) +- Add link to Discord channel in nuget's readme.md +- Document improvements +##### Update on 0.0.10 (2024-03-12) +- Rename `Workflow` to `Graph` +- Rename `AddInitializeMessage` to `SendIntroduction` +- Rename `SequentialGroupChat` to `RoundRobinGroupChat` +##### Update on 0.0.9 (2024-03-02) +- Refactor over @AutoGen.Message and introducing `TextMessage`, `ImageMessage`, `MultiModalMessage` and so on. PR [#1676](https://github.com/microsoft/autogen/pull/1676) +- Add `AutoGen.SemanticKernel` to support seamless integration with Semantic Kernel +- Move the agent contract abstraction to `AutoGen.Core` package. The `AutoGen.Core` package provides the abstraction for message type, agent and group chat and doesn't contain dependencies over `Azure.AI.OpenAI` or `Semantic Kernel`. This is useful when you want to leverage AutoGen's abstraction only and want to avoid introducing any other dependencies. +- Move `GPTAgent`, `OpenAIChatAgent` and all openai-dependencies to `AutoGen.OpenAI` +##### Update on 0.0.8 (2024-02-28) +- Fix [#1804](https://github.com/microsoft/autogen/pull/1804) +- Streaming support for IAgent [#1656](https://github.com/microsoft/autogen/pull/1656) +- Streaming support for middleware via `MiddlewareStreamingAgent` [#1656](https://github.com/microsoft/autogen/pull/1656) +- Graph chat support with conditional transition workflow [#1761](https://github.com/microsoft/autogen/pull/1761) +- AutoGen.SourceGenerator: Generate `FunctionContract` from `FunctionAttribute` [#1736](https://github.com/microsoft/autogen/pull/1736) +##### Update on 0.0.7 (2024-02-11) +- Add `AutoGen.LMStudio` to support comsume openai-like API from LMStudio local server +##### Update on 0.0.6 (2024-01-23) +- Add `MiddlewareAgent` +- Use `MiddlewareAgent` to implement existing agent hooks (RegisterPreProcess, RegisterPostProcess, RegisterReply) +- Remove `AutoReplyAgent`, `PreProcessAgent`, `PostProcessAgent` because they are replaced by `MiddlewareAgent` +##### Update on 0.0.5 +- Simplify `IAgent` interface by removing `ChatLLM` Property +- Add `GenerateReplyOptions` to `IAgent.GenerateReplyAsync` which allows user to specify or override the options when generating reply + +##### Update on 0.0.4 +- Move out dependency of Semantic Kernel +- Add type `IChatLLM` as connector to LLM + +##### Update on 0.0.3 +- In AutoGen.SourceGenerator, rename FunctionAttribution to FunctionAttribute +- In AutoGen, refactor over ConversationAgent, UserProxyAgent, and AssistantAgent + +##### Update on 0.0.2 +- update Azure.OpenAI.AI to 1.0.0-beta.12 +- update Semantic kernel to 1.0.1 diff --git a/dotnet/eng/MetaInfo.props b/dotnet/eng/MetaInfo.props new file mode 100644 index 00000000000..0444dadfd5e --- /dev/null +++ b/dotnet/eng/MetaInfo.props @@ -0,0 +1,12 @@ + + + + 0.0.14 + AutoGen + https://microsoft.github.io/autogen-for-net/ + https://github.com/microsoft/autogen + git + MIT + false + + \ No newline at end of file diff --git a/dotnet/eng/Sign.props b/dotnet/eng/Sign.props new file mode 100644 index 00000000000..0d69e7797e4 --- /dev/null +++ b/dotnet/eng/Sign.props @@ -0,0 +1,22 @@ + + + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + + Microsoft400 + + + + + NuGet + + + diff --git a/dotnet/eng/Version.props b/dotnet/eng/Version.props new file mode 100644 index 00000000000..ae213015471 --- /dev/null +++ b/dotnet/eng/Version.props @@ -0,0 +1,17 @@ + + + + 1.0.0-beta.17 + 1.10.0 + 1.10.0-alpha + 5.0.0 + 4.3.0 + 6.0.0 + 6.8.0 + 2.4.2 + 17.7.0 + 1.0.0-beta.24229.4 + 8.0.0 + 4.0.0 + + \ No newline at end of file diff --git a/dotnet/eng/opensource.snk b/dotnet/eng/opensource.snk new file mode 100644 index 00000000000..779df7c8366 Binary files /dev/null and b/dotnet/eng/opensource.snk differ diff --git a/dotnet/global.json b/dotnet/global.json new file mode 100644 index 00000000000..a604954f983 --- /dev/null +++ b/dotnet/global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "8.0.104", + "rollForward": "latestMinor" + } +} \ No newline at end of file diff --git a/dotnet/nuget/NUGET.md b/dotnet/nuget/NUGET.md new file mode 100644 index 00000000000..34fdbca33ca --- /dev/null +++ b/dotnet/nuget/NUGET.md @@ -0,0 +1,8 @@ +### About AutoGen for .NET +`AutoGen for .NET` is the official .NET SDK for [AutoGen](https://github.com/microsoft/autogen). It enables you to create LLM agents and construct multi-agent workflows with ease. It also provides integration with popular platforms like OpenAI, Semantic Kernel, and LM Studio. + +### Gettings started +- Find documents and examples on our [document site](https://microsoft.github.io/autogen-for-net/) +- Join our [Discord channel](https://discord.gg/pAbnFJrkgZ) to get help and discuss with the community +- Report a bug or request a feature by creating a new issue in our [github repo](https://github.com/microsoft/autogen) +- Consume the nightly build package from one of the [nightly build feeds](https://microsoft.github.io/autogen-for-net/articles/Installation.html#nighly-build) \ No newline at end of file diff --git a/dotnet/nuget/icon.png b/dotnet/nuget/icon.png new file mode 100644 index 00000000000..076fc48c562 --- /dev/null +++ b/dotnet/nuget/icon.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:02dbf31fea0b92714c80fdc90888da7e96374a1f52c621a939835fd3c876ddcc +size 426084 diff --git a/dotnet/nuget/nuget-package.props b/dotnet/nuget/nuget-package.props new file mode 100644 index 00000000000..c6ddf38916f --- /dev/null +++ b/dotnet/nuget/nuget-package.props @@ -0,0 +1,54 @@ + + + true + + + AutoGen + Microsoft + AutoGen + A programming framework for agentic AI + AI, Artificial Intelligence, SDK + $(AssemblyName) + + + MIT + Β© Microsoft Corporation. All rights reserved. + https://microsoft.github.io/autogen-for-net + https://github.com/microsoft/autogen + true + + + icon.png + icon.png + NUGET.md + + + true + snupkg + + + true + + + true + + + bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml + + + + + + + + + + + + + + + + true + + \ No newline at end of file diff --git a/dotnet/sample/AutoGen.BasicSamples/AutoGen.BasicSample.csproj b/dotnet/sample/AutoGen.BasicSamples/AutoGen.BasicSample.csproj new file mode 100644 index 00000000000..0cafff3c0d0 --- /dev/null +++ b/dotnet/sample/AutoGen.BasicSamples/AutoGen.BasicSample.csproj @@ -0,0 +1,25 @@ +ο»Ώ + + + Exe + $(TestTargetFramework) + enable + enable + True + $(NoWarn);CS8981;CS8600;CS8602;CS8604;CS8618;CS0219;SKEXP0054;SKEXP0050;SKEXP0110 + + + + + + + + + + + + + PreserveNewest + + + diff --git a/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/AgentCodeSnippet.cs b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/AgentCodeSnippet.cs new file mode 100644 index 00000000000..abaf94cbd4f --- /dev/null +++ b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/AgentCodeSnippet.cs @@ -0,0 +1,31 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// AgentCodeSnippet.cs +using AutoGen.Core; + +namespace AutoGen.BasicSample.CodeSnippet; + +internal class AgentCodeSnippet +{ + public async Task ChatWithAnAgent(IStreamingAgent agent) + { + #region ChatWithAnAgent_GenerateReplyAsync + var message = new TextMessage(Role.User, "Hello"); + IMessage reply = await agent.GenerateReplyAsync([message]); + #endregion ChatWithAnAgent_GenerateReplyAsync + + #region ChatWithAnAgent_SendAsync + reply = await agent.SendAsync("Hello"); + #endregion ChatWithAnAgent_SendAsync + + #region ChatWithAnAgent_GenerateStreamingReplyAsync + var textMessage = new TextMessage(Role.User, "Hello"); + await foreach (var streamingReply in agent.GenerateStreamingReplyAsync([message])) + { + if (streamingReply is TextMessageUpdate update) + { + Console.Write(update.Content); + } + } + #endregion ChatWithAnAgent_GenerateStreamingReplyAsync + } +} diff --git a/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/BuildInMessageCodeSnippet.cs b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/BuildInMessageCodeSnippet.cs new file mode 100644 index 00000000000..f26485116c8 --- /dev/null +++ b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/BuildInMessageCodeSnippet.cs @@ -0,0 +1,42 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// BuildInMessageCodeSnippet.cs + +using AutoGen.Core; +namespace AutoGen.BasicSample.CodeSnippet; + +internal class BuildInMessageCodeSnippet +{ + public async Task StreamingCallCodeSnippetAsync() + { + IStreamingAgent agent = default; + #region StreamingCallCodeSnippet + var helloTextMessage = new TextMessage(Role.User, "Hello"); + var reply = agent.GenerateStreamingReplyAsync([helloTextMessage]); + var finalTextMessage = new TextMessage(Role.Assistant, string.Empty, from: agent.Name); + await foreach (var message in reply) + { + if (message is TextMessageUpdate textMessage) + { + Console.Write(textMessage.Content); + finalTextMessage.Update(textMessage); + } + } + #endregion StreamingCallCodeSnippet + + #region StreamingCallWithFinalMessage + reply = agent.GenerateStreamingReplyAsync([helloTextMessage]); + TextMessage finalMessage = null; + await foreach (var message in reply) + { + if (message is TextMessageUpdate textMessage) + { + Console.Write(textMessage.Content); + } + else if (message is TextMessage txtMessage) + { + finalMessage = txtMessage; + } + } + #endregion StreamingCallWithFinalMessage + } +} diff --git a/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/CreateAnAgent.cs b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/CreateAnAgent.cs new file mode 100644 index 00000000000..4833c6195c9 --- /dev/null +++ b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/CreateAnAgent.cs @@ -0,0 +1,142 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// CreateAnAgent.cs + +using AutoGen; +using AutoGen.Core; +using AutoGen.OpenAI; +using FluentAssertions; + +public partial class AssistantCodeSnippet +{ + public void CodeSnippet1() + { + #region code_snippet_1 + // get OpenAI Key and create config + var openAIKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new Exception("Please set OPENAI_API_KEY environment variable."); + var llmConfig = new OpenAIConfig(openAIKey, "gpt-3.5-turbo"); + + // create assistant agent + var assistantAgent = new AssistantAgent( + name: "assistant", + systemMessage: "You are an assistant that help user to do some tasks.", + llmConfig: new ConversableAgentConfig + { + Temperature = 0, + ConfigList = new[] { llmConfig }, + }); + #endregion code_snippet_1 + + } + + public void CodeSnippet2() + { + #region code_snippet_2 + // get OpenAI Key and create config + var apiKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY"); + string endPoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT"); // change to your endpoint + + var llmConfig = new AzureOpenAIConfig( + endpoint: endPoint, + deploymentName: "gpt-3.5-turbo-16k", // change to your deployment name + apiKey: apiKey); + + // create assistant agent + var assistantAgent = new AssistantAgent( + name: "assistant", + systemMessage: "You are an assistant that help user to do some tasks.", + llmConfig: new ConversableAgentConfig + { + Temperature = 0, + ConfigList = new[] { llmConfig }, + }); + #endregion code_snippet_2 + } + + #region code_snippet_3 + /// + /// convert input to upper case + /// + /// input + [Function] + public async Task UpperCase(string input) + { + var result = input.ToUpper(); + return result; + } + + #endregion code_snippet_3 + + public async Task CodeSnippet4() + { + // get OpenAI Key and create config + var apiKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY"); + string endPoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT"); // change to your endpoint + + var llmConfig = new AzureOpenAIConfig( + endpoint: endPoint, + deploymentName: "gpt-3.5-turbo-16k", // change to your deployment name + apiKey: apiKey); + #region code_snippet_4 + var assistantAgent = new AssistantAgent( + name: "assistant", + systemMessage: "You are an assistant that convert user input to upper case.", + llmConfig: new ConversableAgentConfig + { + Temperature = 0, + ConfigList = new[] + { + llmConfig + }, + FunctionContracts = new[] + { + this.UpperCaseFunctionContract, // The FunctionDefinition object for the UpperCase function + }, + }); + + var response = await assistantAgent.SendAsync("hello"); + response.Should().BeOfType(); + var toolCallMessage = (ToolCallMessage)response; + toolCallMessage.ToolCalls.Count().Should().Be(1); + toolCallMessage.ToolCalls.First().FunctionName.Should().Be("UpperCase"); + #endregion code_snippet_4 + } + + public async Task CodeSnippet5() + { + // get OpenAI Key and create config + var apiKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY"); + string endPoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT"); // change to your endpoint + + var llmConfig = new AzureOpenAIConfig( + endpoint: endPoint, + deploymentName: "gpt-3.5-turbo-16k", // change to your deployment name + apiKey: apiKey); + #region code_snippet_5 + var assistantAgent = new AssistantAgent( + name: "assistant", + systemMessage: "You are an assistant that convert user input to upper case.", + llmConfig: new ConversableAgentConfig + { + Temperature = 0, + ConfigList = new[] + { + llmConfig + }, + FunctionContracts = new[] + { + this.UpperCaseFunctionContract, // The FunctionDefinition object for the UpperCase function + }, + }, + functionMap: new Dictionary>> + { + { this.UpperCaseFunction.Name, this.UpperCaseWrapper }, // The wrapper function for the UpperCase function + }); + + var response = await assistantAgent.SendAsync("hello"); + response.Should().BeOfType(); + response.From.Should().Be("assistant"); + var textMessage = (TextMessage)response; + textMessage.Content.Should().Be("HELLO"); + #endregion code_snippet_5 + } +} diff --git a/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/FunctionCallCodeSnippet.cs b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/FunctionCallCodeSnippet.cs new file mode 100644 index 00000000000..2b7e25fee0c --- /dev/null +++ b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/FunctionCallCodeSnippet.cs @@ -0,0 +1,149 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// FunctionCallCodeSnippet.cs + +using AutoGen; +using AutoGen.Core; +using AutoGen.OpenAI; +using FluentAssertions; + +public partial class FunctionCallCodeSnippet +{ + public async Task CodeSnippet4() + { + // get OpenAI Key and create config + var apiKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY"); + string endPoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT"); // change to your endpoint + + var llmConfig = new AzureOpenAIConfig( + endpoint: endPoint, + deploymentName: "gpt-3.5-turbo-16k", // change to your deployment name + apiKey: apiKey); + #region code_snippet_4 + var function = new TypeSafeFunctionCall(); + var assistantAgent = new AssistantAgent( + name: "assistant", + systemMessage: "You are an assistant that convert user input to upper case.", + llmConfig: new ConversableAgentConfig + { + Temperature = 0, + ConfigList = new[] + { + llmConfig + }, + FunctionContracts = new[] + { + function.WeatherReportFunctionContract, + }, + }); + + var response = await assistantAgent.SendAsync("hello What's the weather in Seattle today? today is 2024-01-01"); + response.Should().BeOfType(); + var toolCallMessage = (ToolCallMessage)response; + toolCallMessage.ToolCalls.Count().Should().Be(1); + toolCallMessage.ToolCalls[0].FunctionName.Should().Be("WeatherReport"); + toolCallMessage.ToolCalls[0].FunctionArguments.Should().Be(@"{""location"":""Seattle"",""date"":""2024-01-01""}"); + #endregion code_snippet_4 + } + + + public async Task CodeSnippet6() + { + // get OpenAI Key and create config + var apiKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY"); + string endPoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT"); // change to your endpoint + + var llmConfig = new AzureOpenAIConfig( + endpoint: endPoint, + deploymentName: "gpt-3.5-turbo-16k", // change to your deployment name + apiKey: apiKey); + #region code_snippet_6 + var function = new TypeSafeFunctionCall(); + var assistantAgent = new AssistantAgent( + name: "assistant", + llmConfig: new ConversableAgentConfig + { + Temperature = 0, + ConfigList = new[] + { + llmConfig + }, + FunctionContracts = new[] + { + function.WeatherReportFunctionContract, + }, + }, + functionMap: new Dictionary>> + { + { function.WeatherReportFunctionContract.Name, function.WeatherReportWrapper }, // The function wrapper for the weather report function + }); + + #endregion code_snippet_6 + + #region code_snippet_6_1 + var response = await assistantAgent.SendAsync("What's the weather in Seattle today? today is 2024-01-01"); + response.Should().BeOfType(); + var textMessage = (TextMessage)response; + textMessage.Content.Should().Be("Weather report for Seattle on 2024-01-01 is sunny"); + #endregion code_snippet_6_1 + } + + public async Task OverriderFunctionContractAsync() + { + IAgent agent = default; + IEnumerable messages = new List(); + #region overrider_function_contract + var function = new TypeSafeFunctionCall(); + var reply = agent.GenerateReplyAsync(messages, new GenerateReplyOptions + { + Functions = new[] { function.WeatherReportFunctionContract }, + }); + #endregion overrider_function_contract + } + + public async Task RegisterFunctionCallMiddlewareAsync() + { + IAgent agent = default; + #region register_function_call_middleware + var function = new TypeSafeFunctionCall(); + var functionCallMiddleware = new FunctionCallMiddleware( + functions: new[] { function.WeatherReportFunctionContract }, + functionMap: new Dictionary>> + { + { function.WeatherReportFunctionContract.Name, function.WeatherReportWrapper }, + }); + + agent = agent!.RegisterMiddleware(functionCallMiddleware); + var reply = await agent.SendAsync("What's the weather in Seattle today? today is 2024-01-01"); + #endregion register_function_call_middleware + } + + public async Task TwoAgentWeatherChatTestAsync() + { + var key = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY") ?? throw new ArgumentException("AZURE_OPENAI_API_KEY is not set"); + var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new ArgumentException("AZURE_OPENAI_ENDPOINT is not set"); + var deploymentName = "gpt-35-turbo-16k"; + var config = new AzureOpenAIConfig(endpoint, deploymentName, key); + #region two_agent_weather_chat + var function = new TypeSafeFunctionCall(); + var assistant = new AssistantAgent( + "assistant", + llmConfig: new ConversableAgentConfig + { + ConfigList = new[] { config }, + FunctionContracts = new[] + { + function.WeatherReportFunctionContract, + }, + }); + + var user = new UserProxyAgent( + name: "user", + functionMap: new Dictionary>> + { + { function.WeatherReportFunctionContract.Name, function.WeatherReportWrapper }, + }); + + await user.InitiateChatAsync(assistant, "what's weather in Seattle today, today is 2024-01-01", 10); + #endregion two_agent_weather_chat + } +} diff --git a/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/GetStartCodeSnippet.cs b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/GetStartCodeSnippet.cs new file mode 100644 index 00000000000..fe97152183a --- /dev/null +++ b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/GetStartCodeSnippet.cs @@ -0,0 +1,41 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// GetStartCodeSnippet.cs + +#region snippet_GetStartCodeSnippet +using AutoGen; +using AutoGen.Core; +using AutoGen.OpenAI; +#endregion snippet_GetStartCodeSnippet + +public class GetStartCodeSnippet +{ + public async Task CodeSnippet1() + { + #region code_snippet_1 + var openAIKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new Exception("Please set OPENAI_API_KEY environment variable."); + var gpt35Config = new OpenAIConfig(openAIKey, "gpt-3.5-turbo"); + + var assistantAgent = new AssistantAgent( + name: "assistant", + systemMessage: "You are an assistant that help user to do some tasks.", + llmConfig: new ConversableAgentConfig + { + Temperature = 0, + ConfigList = [gpt35Config], + }) + .RegisterPrintMessage(); // register a hook to print message nicely to console + + // set human input mode to ALWAYS so that user always provide input + var userProxyAgent = new UserProxyAgent( + name: "user", + humanInputMode: HumanInputMode.ALWAYS) + .RegisterPrintMessage(); + + // start the conversation + await userProxyAgent.InitiateChatAsync( + receiver: assistantAgent, + message: "Hey assistant, please do me a favor.", + maxRound: 10); + #endregion code_snippet_1 + } +} diff --git a/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/MiddlewareAgentCodeSnippet.cs b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/MiddlewareAgentCodeSnippet.cs new file mode 100644 index 00000000000..320afd0de67 --- /dev/null +++ b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/MiddlewareAgentCodeSnippet.cs @@ -0,0 +1,169 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// MiddlewareAgentCodeSnippet.cs + +using System.Text.Json; +using AutoGen.Core; +using AutoGen.OpenAI; +using FluentAssertions; + +namespace AutoGen.BasicSample.CodeSnippet; + +public class MiddlewareAgentCodeSnippet +{ + public async Task CreateMiddlewareAgentAsync() + { + #region create_middleware_agent_with_original_agent + // Create an agent that always replies "Hello World" + IAgent agent = new DefaultReplyAgent(name: "assistant", defaultReply: "Hello World"); + + // Create a middleware agent on top of default reply agent + var middlewareAgent = new MiddlewareAgent(innerAgent: agent); + middlewareAgent.Use(async (messages, options, agent, ct) => + { + var lastMessage = messages.Last() as TextMessage; + lastMessage.Content = $"[middleware 0] {lastMessage.Content}"; + return await agent.GenerateReplyAsync(messages, options, ct); + }); + + var reply = await middlewareAgent.SendAsync("Hello World"); + reply.GetContent().Should().Be("[middleware 0] Hello World"); + #endregion create_middleware_agent_with_original_agent + + #region register_middleware_agent + middlewareAgent = agent.RegisterMiddleware(async (messages, options, agent, ct) => + { + var lastMessage = messages.Last() as TextMessage; + lastMessage.Content = $"[middleware 0] {lastMessage.Content}"; + return await agent.GenerateReplyAsync(messages, options, ct); + }); + #endregion register_middleware_agent + + #region short_circuit_middleware_agent + // This middleware will short circuit the agent and return the last message directly. + middlewareAgent.Use(async (messages, options, agent, ct) => + { + var lastMessage = messages.Last() as TextMessage; + lastMessage.Content = $"[middleware shortcut]"; + return lastMessage; + }); + #endregion short_circuit_middleware_agent + } + + public async Task RegisterStreamingMiddlewareAsync() + { + IStreamingAgent streamingAgent = default; + #region register_streaming_middleware + var connector = new OpenAIChatRequestMessageConnector(); + var agent = streamingAgent! + .RegisterStreamingMiddleware(connector); + #endregion register_streaming_middleware + } + + public async Task CodeSnippet1() + { + #region code_snippet_1 + // Create an agent that always replies "Hello World" + IAgent agent = new DefaultReplyAgent(name: "assistant", defaultReply: "Hello World"); + + // Create a middleware agent on top of default reply agent + var middlewareAgent = new MiddlewareAgent(innerAgent: agent); + + // Since no middleware is added, middlewareAgent will simply proxy into the inner agent to generate reply. + var reply = await middlewareAgent.SendAsync("Hello World"); + reply.From.Should().Be("assistant"); + reply.GetContent().Should().Be("Hello World"); + #endregion code_snippet_1 + + #region code_snippet_2 + middlewareAgent.Use(async (messages, options, agent, ct) => + { + var lastMessage = messages.Last() as TextMessage; + lastMessage.Content = $"[middleware 0] {lastMessage.Content}"; + return await agent.GenerateReplyAsync(messages, options, ct); + }); + + reply = await middlewareAgent.SendAsync("Hello World"); + reply.Should().BeOfType(); + var textReply = (TextMessage)reply; + textReply.Content.Should().Be("[middleware 0] Hello World"); + #endregion code_snippet_2 + #region code_snippet_2_1 + middlewareAgent = agent.RegisterMiddleware(async (messages, options, agnet, ct) => + { + var lastMessage = messages.Last() as TextMessage; + lastMessage.Content = $"[middleware 0] {lastMessage.Content}"; + return await agent.GenerateReplyAsync(messages, options, ct); + }); + + reply = await middlewareAgent.SendAsync("Hello World"); + reply.GetContent().Should().Be("[middleware 0] Hello World"); + #endregion code_snippet_2_1 + #region code_snippet_3 + middlewareAgent.Use(async (messages, options, agent, ct) => + { + var lastMessage = messages.Last() as TextMessage; + lastMessage.Content = $"[middleware 1] {lastMessage.Content}"; + return await agent.GenerateReplyAsync(messages, options, ct); + }); + + reply = await middlewareAgent.SendAsync("Hello World"); + reply.GetContent().Should().Be("[middleware 0] [middleware 1] Hello World"); + #endregion code_snippet_3 + + #region code_snippet_4 + middlewareAgent.Use(async (messages, options, next, ct) => + { + var lastMessage = messages.Last() as TextMessage; + lastMessage.Content = $"[middleware shortcut]"; + + return lastMessage; + }); + + reply = await middlewareAgent.SendAsync("Hello World"); + reply.GetContent().Should().Be("[middleware shortcut]"); + #endregion code_snippet_4 + + #region retrieve_inner_agent + var innerAgent = middlewareAgent.Agent; + #endregion retrieve_inner_agent + + #region code_snippet_logging_to_console + var agentWithLogging = middlewareAgent.RegisterMiddleware(async (messages, options, agent, ct) => + { + var reply = await agent.GenerateReplyAsync(messages, options, ct); + var formattedMessage = reply.FormatMessage(); + Console.WriteLine(formattedMessage); + + return reply; + }); + #endregion code_snippet_logging_to_console + + #region code_snippet_response_format_forcement + var jsonAgent = middlewareAgent.RegisterMiddleware(async (messages, options, agent, ct) => + { + var maxAttempt = 5; + var reply = await agent.GenerateReplyAsync(messages, options, ct); + while (maxAttempt-- > 0) + { + if (JsonSerializer.Deserialize>(reply.GetContent()) is { } dict) + { + return reply; + } + else + { + await Task.Delay(1000); + var reviewPrompt = @"The format is not json, please modify your response to json format + -- ORIGINAL MESSAGE -- + {reply.Content} + -- END OF ORIGINAL MESSAGE -- + + Reply again with json format."; + reply = await agent.SendAsync(reviewPrompt, messages, ct); + } + } + + throw new Exception("agent fails to generate json response"); + }); + #endregion code_snippet_response_format_forcement + } +} diff --git a/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/MistralAICodeSnippet.cs b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/MistralAICodeSnippet.cs new file mode 100644 index 00000000000..0ce1d840d36 --- /dev/null +++ b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/MistralAICodeSnippet.cs @@ -0,0 +1,86 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// MistralAICodeSnippet.cs + +#region using_statement +using AutoGen.Core; +using AutoGen.Mistral; +using AutoGen.Mistral.Extension; +using FluentAssertions; +#endregion using_statement + +namespace AutoGen.BasicSample.CodeSnippet; + +#region weather_function +public partial class MistralAgentFunction +{ + [Function] + public async Task GetWeather(string location) + { + return "The weather in " + location + " is sunny."; + } +} +#endregion weather_function + +internal class MistralAICodeSnippet +{ + public async Task CreateMistralAIClientAsync() + { + #region create_mistral_agent + var apiKey = Environment.GetEnvironmentVariable("MISTRAL_API_KEY") ?? throw new Exception("Missing MISTRAL_API_KEY environment variable"); + var client = new MistralClient(apiKey: apiKey); + var agent = new MistralClientAgent( + client: client, + name: "MistralAI", + model: MistralAIModelID.OPEN_MISTRAL_7B) + .RegisterMessageConnector(); // support more AutoGen built-in message types. + + await agent.SendAsync("Hello, how are you?"); + #endregion create_mistral_agent + + #region streaming_chat + var reply = agent.GenerateStreamingReplyAsync( + messages: [new TextMessage(Role.User, "Hello, how are you?")] + ); + + await foreach (var message in reply) + { + if (message is TextMessageUpdate textMessageUpdate && textMessageUpdate.Content is string content) + { + Console.WriteLine(content); + } + } + #endregion streaming_chat + } + + public async Task MistralAIChatAgentGetWeatherToolUsageAsync() + { + #region create_mistral_function_call_agent + var apiKey = Environment.GetEnvironmentVariable("MISTRAL_API_KEY") ?? throw new Exception("Missing MISTRAL_API_KEY environment variable"); + var client = new MistralClient(apiKey: apiKey); + var agent = new MistralClientAgent( + client: client, + name: "MistralAI", + model: MistralAIModelID.MISTRAL_SMALL_LATEST) + .RegisterMessageConnector(); // support more AutoGen built-in message types like ToolCallMessage and ToolCallResultMessage + #endregion create_mistral_function_call_agent + + #region create_get_weather_function_call_middleware + var mistralFunctions = new MistralAgentFunction(); + var functionCallMiddleware = new FunctionCallMiddleware( + functions: [mistralFunctions.GetWeatherFunctionContract], + functionMap: new Dictionary>> // with functionMap, the function will be automatically triggered if the tool name matches one of the keys. + { + { mistralFunctions.GetWeatherFunctionContract.Name, mistralFunctions.GetWeather } + }); + #endregion create_get_weather_function_call_middleware + + #region register_function_call_middleware + agent = agent.RegisterStreamingMiddleware(functionCallMiddleware); + #endregion register_function_call_middleware + + #region send_message_with_function_call + var reply = await agent.SendAsync("What is the weather in Seattle?"); + reply.GetContent().Should().Be("The weather in Seattle is sunny."); + #endregion send_message_with_function_call + } +} diff --git a/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/OpenAICodeSnippet.cs b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/OpenAICodeSnippet.cs new file mode 100644 index 00000000000..022f7e9f984 --- /dev/null +++ b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/OpenAICodeSnippet.cs @@ -0,0 +1,136 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// OpenAICodeSnippet.cs + +#region using_statement +using AutoGen.Core; +using AutoGen.OpenAI; +using AutoGen.OpenAI.Extension; +using Azure.AI.OpenAI; +#endregion using_statement +using FluentAssertions; + +namespace AutoGen.BasicSample.CodeSnippet; +#region weather_function +public partial class Functions +{ + [Function] + public async Task GetWeather(string location) + { + return "The weather in " + location + " is sunny."; + } +} +#endregion weather_function +public partial class OpenAICodeSnippet +{ + [Function] + public async Task GetWeather(string location) + { + return "The weather in " + location + " is sunny."; + } + + public async Task CreateOpenAIChatAgentAsync() + { + #region create_openai_chat_agent + var openAIKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new Exception("Please set OPENAI_API_KEY environment variable."); + var modelId = "gpt-3.5-turbo"; + var openAIClient = new OpenAIClient(openAIKey); + + // create an open ai chat agent + var openAIChatAgent = new OpenAIChatAgent( + openAIClient: openAIClient, + name: "assistant", + modelName: modelId, + systemMessage: "You are an assistant that help user to do some tasks."); + + // OpenAIChatAgent supports the following message types: + // - IMessage where ChatRequestMessage is from Azure.AI.OpenAI + + var helloMessage = new ChatRequestUserMessage("Hello"); + + // Use MessageEnvelope.Create to create an IMessage + var chatMessageContent = MessageEnvelope.Create(helloMessage); + var reply = await openAIChatAgent.SendAsync(chatMessageContent); + + // The type of reply is MessageEnvelope where ChatResponseMessage is from Azure.AI.OpenAI + reply.Should().BeOfType>(); + + // You can un-envelop the reply to get the ChatResponseMessage + ChatResponseMessage response = reply.As>().Content; + response.Role.Should().Be(ChatRole.Assistant); + #endregion create_openai_chat_agent + + #region create_openai_chat_agent_streaming + var streamingReply = openAIChatAgent.GenerateStreamingReplyAsync(new[] { chatMessageContent }); + + await foreach (var streamingMessage in streamingReply) + { + streamingMessage.Should().BeOfType>(); + streamingMessage.As>().Content.Role.Should().Be(ChatRole.Assistant); + } + #endregion create_openai_chat_agent_streaming + + #region register_openai_chat_message_connector + // register message connector to support more message types + var agentWithConnector = openAIChatAgent + .RegisterMessageConnector(); + + // now the agentWithConnector supports more message types + var messages = new IMessage[] + { + MessageEnvelope.Create(new ChatRequestUserMessage("Hello")), + new TextMessage(Role.Assistant, "Hello", from: "user"), + new MultiModalMessage(Role.Assistant, + [ + new TextMessage(Role.Assistant, "Hello", from: "user"), + ], + from: "user"), + new Message(Role.Assistant, "Hello", from: "user"), // Message type is going to be deprecated, please use TextMessage instead + }; + + foreach (var message in messages) + { + reply = await agentWithConnector.SendAsync(message); + + reply.Should().BeOfType(); + reply.As().From.Should().Be("assistant"); + } + #endregion register_openai_chat_message_connector + } + + public async Task OpenAIChatAgentGetWeatherFunctionCallAsync() + { + #region openai_chat_agent_get_weather_function_call + var openAIKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new Exception("Please set OPENAI_API_KEY environment variable."); + var modelId = "gpt-3.5-turbo"; + var openAIClient = new OpenAIClient(openAIKey); + + // create an open ai chat agent + var openAIChatAgent = new OpenAIChatAgent( + openAIClient: openAIClient, + name: "assistant", + modelName: modelId, + systemMessage: "You are an assistant that help user to do some tasks.") + .RegisterMessageConnector(); + + #endregion openai_chat_agent_get_weather_function_call + + #region create_function_call_middleware + var functions = new Functions(); + var functionCallMiddleware = new FunctionCallMiddleware( + functions: [functions.GetWeatherFunctionContract], // GetWeatherFunctionContract is auto-generated from the GetWeather function + functionMap: new Dictionary>> + { + { functions.GetWeatherFunctionContract.Name, functions.GetWeatherWrapper } // GetWeatherWrapper is a wrapper function for GetWeather, which is also auto-generated + }); + + openAIChatAgent = openAIChatAgent.RegisterStreamingMiddleware(functionCallMiddleware); + #endregion create_function_call_middleware + + #region chat_agent_send_function_call + var reply = await openAIChatAgent.SendAsync("what is the weather in Seattle?"); + reply.GetContent().Should().Be("The weather in Seattle is sunny."); + reply.GetToolCalls().Count.Should().Be(1); + reply.GetToolCalls().First().Should().Be(this.GetWeatherFunctionContract.Name); + #endregion chat_agent_send_function_call + } +} diff --git a/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/PrintMessageMiddlewareCodeSnippet.cs b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/PrintMessageMiddlewareCodeSnippet.cs new file mode 100644 index 00000000000..bf4f9c976e2 --- /dev/null +++ b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/PrintMessageMiddlewareCodeSnippet.cs @@ -0,0 +1,44 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// PrintMessageMiddlewareCodeSnippet.cs + +using AutoGen.Core; +using AutoGen.OpenAI; +using AutoGen.OpenAI.Extension; +using Azure; +using Azure.AI.OpenAI; + +namespace AutoGen.BasicSample.CodeSnippet; + +internal class PrintMessageMiddlewareCodeSnippet +{ + public async Task PrintMessageMiddlewareAsync() + { + var config = LLMConfiguration.GetAzureOpenAIGPT3_5_Turbo(); + var endpoint = new Uri(config.Endpoint); + var openaiClient = new OpenAIClient(endpoint, new AzureKeyCredential(config.ApiKey)); + var agent = new OpenAIChatAgent(openaiClient, "assistant", config.DeploymentName) + .RegisterMessageConnector(); + + #region PrintMessageMiddleware + var agentWithPrintMessageMiddleware = agent + .RegisterPrintMessage(); + + await agentWithPrintMessageMiddleware.SendAsync("write a long poem"); + #endregion PrintMessageMiddleware + } + + public async Task PrintMessageStreamingMiddlewareAsync() + { + var config = LLMConfiguration.GetAzureOpenAIGPT3_5_Turbo(); + var endpoint = new Uri(config.Endpoint); + var openaiClient = new OpenAIClient(endpoint, new AzureKeyCredential(config.ApiKey)); + + #region print_message_streaming + var streamingAgent = new OpenAIChatAgent(openaiClient, "assistant", config.DeploymentName) + .RegisterMessageConnector() + .RegisterPrintMessage(); + + await streamingAgent.SendAsync("write a long poem"); + #endregion print_message_streaming + } +} diff --git a/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/RunCodeSnippetCodeSnippet.cs b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/RunCodeSnippetCodeSnippet.cs new file mode 100644 index 00000000000..e498650b6aa --- /dev/null +++ b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/RunCodeSnippetCodeSnippet.cs @@ -0,0 +1,48 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// RunCodeSnippetCodeSnippet.cs + +#region code_snippet_0_1 +using AutoGen.Core; +using AutoGen.DotnetInteractive; +#endregion code_snippet_0_1 + +namespace AutoGen.BasicSample.CodeSnippet; +public class RunCodeSnippetCodeSnippet +{ + public async Task CodeSnippet1() + { + IAgent agent = default; + + #region code_snippet_1_1 + var workingDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + Directory.CreateDirectory(workingDirectory); + var interactiveService = new InteractiveService(installingDirectory: workingDirectory); + await interactiveService.StartAsync(workingDirectory: workingDirectory); + #endregion code_snippet_1_1 + + #region code_snippet_1_2 + // register dotnet code block execution hook to an arbitrary agent + var dotnetCodeAgent = agent.RegisterDotnetCodeBlockExectionHook(interactiveService: interactiveService); + + var codeSnippet = @" + ```csharp + Console.WriteLine(""Hello World""); + ```"; + + await dotnetCodeAgent.SendAsync(codeSnippet); + // output: Hello World + #endregion code_snippet_1_2 + + #region code_snippet_1_3 + var content = @" + ```csharp + // This is csharp code snippet + ``` + + ```python + // This is python code snippet + ``` + "; + #endregion code_snippet_1_3 + } +} diff --git a/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/SemanticKernelCodeSnippet.cs b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/SemanticKernelCodeSnippet.cs new file mode 100644 index 00000000000..20dd12d90ce --- /dev/null +++ b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/SemanticKernelCodeSnippet.cs @@ -0,0 +1,101 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// SemanticKernelCodeSnippet.cs + +using AutoGen.Core; +using AutoGen.SemanticKernel; +using AutoGen.SemanticKernel.Extension; +using FluentAssertions; +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.ChatCompletion; + +namespace AutoGen.BasicSample.CodeSnippet; + +public class SemanticKernelCodeSnippet +{ + public async Task GetWeather(string location) + { + return "The weather in " + location + " is sunny."; + } + public async Task CreateSemanticKernelAgentAsync() + { + #region create_semantic_kernel_agent + var openAIKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new Exception("Please set OPENAI_API_KEY environment variable."); + var modelId = "gpt-3.5-turbo"; + var builder = Kernel.CreateBuilder() + .AddOpenAIChatCompletion(modelId: modelId, apiKey: openAIKey); + var kernel = builder.Build(); + + // create a semantic kernel agent + var semanticKernelAgent = new SemanticKernelAgent( + kernel: kernel, + name: "assistant", + systemMessage: "You are an assistant that help user to do some tasks."); + + // SemanticKernelAgent supports the following message types: + // - IMessage where ChatMessageContent is from Azure.AI.OpenAI + + var helloMessage = new ChatMessageContent(AuthorRole.User, "Hello"); + + // Use MessageEnvelope.Create to create an IMessage + var chatMessageContent = MessageEnvelope.Create(helloMessage); + var reply = await semanticKernelAgent.SendAsync(chatMessageContent); + + // The type of reply is MessageEnvelope where ChatResponseMessage is from Azure.AI.OpenAI + reply.Should().BeOfType>(); + + // You can un-envelop the reply to get the ChatResponseMessage + ChatMessageContent response = reply.As>().Content; + response.Role.Should().Be(AuthorRole.Assistant); + #endregion create_semantic_kernel_agent + + #region create_semantic_kernel_agent_streaming + var streamingReply = semanticKernelAgent.GenerateStreamingReplyAsync(new[] { chatMessageContent }); + + await foreach (var streamingMessage in streamingReply) + { + streamingMessage.Should().BeOfType>(); + streamingMessage.As>().From.Should().Be("assistant"); + } + #endregion create_semantic_kernel_agent_streaming + } + + public async Task SemanticKernelChatMessageContentConnector() + { + #region register_semantic_kernel_chat_message_content_connector + var openAIKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new Exception("Please set OPENAI_API_KEY environment variable."); + var modelId = "gpt-3.5-turbo"; + var builder = Kernel.CreateBuilder() + .AddOpenAIChatCompletion(modelId: modelId, apiKey: openAIKey); + var kernel = builder.Build(); + + // create a semantic kernel agent + var semanticKernelAgent = new SemanticKernelAgent( + kernel: kernel, + name: "assistant", + systemMessage: "You are an assistant that help user to do some tasks."); + + // Register the connector middleware to the kernel agent + var semanticKernelAgentWithConnector = semanticKernelAgent + .RegisterMessageConnector(); + + // now semanticKernelAgentWithConnector supports more message types + IMessage[] messages = [ + MessageEnvelope.Create(new ChatMessageContent(AuthorRole.User, "Hello")), + new TextMessage(Role.Assistant, "Hello", from: "user"), + new MultiModalMessage(Role.Assistant, + [ + new TextMessage(Role.Assistant, "Hello", from: "user"), + ], + from: "user"), + ]; + + foreach (var message in messages) + { + var reply = await semanticKernelAgentWithConnector.SendAsync(message); + + // SemanticKernelChatMessageContentConnector will convert the reply message to TextMessage + reply.Should().BeOfType(); + } + #endregion register_semantic_kernel_chat_message_content_connector + } +} diff --git a/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/TypeSafeFunctionCallCodeSnippet.cs b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/TypeSafeFunctionCallCodeSnippet.cs new file mode 100644 index 00000000000..50bcd8a8048 --- /dev/null +++ b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/TypeSafeFunctionCallCodeSnippet.cs @@ -0,0 +1,121 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// TypeSafeFunctionCallCodeSnippet.cs + +using System.Text.Json; +using AutoGen.OpenAI.Extension; +using Azure.AI.OpenAI; +#region weather_report_using_statement +using AutoGen.Core; +#endregion weather_report_using_statement + +#region weather_report +public partial class TypeSafeFunctionCall +{ + /// + /// Get weather report + /// + /// city + /// date + [Function] + public async Task WeatherReport(string city, string date) + { + return $"Weather report for {city} on {date} is sunny"; + } +} +#endregion weather_report + +public partial class TypeSafeFunctionCall +{ + public async Task Consume() + { + #region weather_report_consume + var functionInstance = new TypeSafeFunctionCall(); + + // Get the generated function definition + FunctionDefinition functionDefiniton = functionInstance.WeatherReportFunctionContract.ToOpenAIFunctionDefinition(); + + // Get the generated function wrapper + Func> functionWrapper = functionInstance.WeatherReportWrapper; + + // ... + #endregion weather_report_consume + } +} +#region code_snippet_3 +// file: FunctionCall.cs + +public partial class TypeSafeFunctionCall +{ + /// + /// convert input to upper case + /// + /// input + [Function] + public async Task UpperCase(string input) + { + var result = input.ToUpper(); + return result; + } +} +#endregion code_snippet_3 + +public class TypeSafeFunctionCallCodeSnippet +{ + public async Task UpperCase(string input) + { + var result = input.ToUpper(); + return result; + } + + #region code_snippet_1 + // file: FunctionDefinition.generated.cs + public FunctionDefinition UpperCaseFunction + { + get => new FunctionDefinition + { + Name = @"UpperCase", + Description = "convert input to upper case", + Parameters = BinaryData.FromObjectAsJson(new + { + Type = "object", + Properties = new + { + input = new + { + Type = @"string", + Description = @"input", + }, + }, + Required = new[] + { + "input", + }, + }, + new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + }) + }; + } + #endregion code_snippet_1 + + #region code_snippet_2 + // file: FunctionDefinition.generated.cs + private class UpperCaseSchema + { + public string input { get; set; } + } + + public Task UpperCaseWrapper(string arguments) + { + var schema = JsonSerializer.Deserialize( + arguments, + new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + }); + + return UpperCase(schema.input); + } + #endregion code_snippet_2 +} diff --git a/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/UserProxyAgentCodeSnippet.cs b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/UserProxyAgentCodeSnippet.cs new file mode 100644 index 00000000000..85aecae959e --- /dev/null +++ b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/UserProxyAgentCodeSnippet.cs @@ -0,0 +1,20 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// UserProxyAgentCodeSnippet.cs +using AutoGen.Core; + +namespace AutoGen.BasicSample.CodeSnippet; + +public class UserProxyAgentCodeSnippet +{ + public async Task CodeSnippet1() + { + #region code_snippet_1 + // create a user proxy agent which always ask user for input + var agent = new UserProxyAgent( + name: "user", + humanInputMode: HumanInputMode.ALWAYS); + + await agent.SendAsync("hello"); + #endregion code_snippet_1 + } +} diff --git a/dotnet/sample/AutoGen.BasicSamples/Example01_AssistantAgent.cs b/dotnet/sample/AutoGen.BasicSamples/Example01_AssistantAgent.cs new file mode 100644 index 00000000000..3ee363bfc06 --- /dev/null +++ b/dotnet/sample/AutoGen.BasicSamples/Example01_AssistantAgent.cs @@ -0,0 +1,46 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// Example01_AssistantAgent.cs + +using AutoGen; +using AutoGen.BasicSample; +using AutoGen.Core; +using FluentAssertions; + +/// +/// This example shows the basic usage of class. +/// +public static class Example01_AssistantAgent +{ + public static async Task RunAsync() + { + var gpt35 = LLMConfiguration.GetAzureOpenAIGPT3_5_Turbo(); + var config = new ConversableAgentConfig + { + Temperature = 0, + ConfigList = [gpt35], + }; + + // create assistant agent + var assistantAgent = new AssistantAgent( + name: "assistant", + systemMessage: "You convert what user said to all uppercase.", + llmConfig: config) + .RegisterPrintMessage(); + + // talk to the assistant agent + var reply = await assistantAgent.SendAsync("hello world"); + reply.Should().BeOfType(); + reply.GetContent().Should().Be("HELLO WORLD"); + + // to carry on the conversation, pass the previous conversation history to the next call + var conversationHistory = new List + { + new TextMessage(Role.User, "hello world"), // first message + reply, // reply from assistant agent + }; + + reply = await assistantAgent.SendAsync("hello world again", conversationHistory); + reply.Should().BeOfType(); + reply.GetContent().Should().Be("HELLO WORLD AGAIN"); + } +} diff --git a/dotnet/sample/AutoGen.BasicSamples/Example02_TwoAgent_MathChat.cs b/dotnet/sample/AutoGen.BasicSamples/Example02_TwoAgent_MathChat.cs new file mode 100644 index 00000000000..c2957f32da7 --- /dev/null +++ b/dotnet/sample/AutoGen.BasicSamples/Example02_TwoAgent_MathChat.cs @@ -0,0 +1,80 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// Example02_TwoAgent_MathChat.cs + +using AutoGen; +using AutoGen.BasicSample; +using AutoGen.Core; +using FluentAssertions; +public static class Example02_TwoAgent_MathChat +{ + public static async Task RunAsync() + { + #region code_snippet_1 + // get gpt-3.5-turbo config + var gpt35 = LLMConfiguration.GetAzureOpenAIGPT3_5_Turbo(); + + // create teacher agent + // teacher agent will create math questions + var teacher = new AssistantAgent( + name: "teacher", + systemMessage: @"You are a teacher that create pre-school math question for student and check answer. + If the answer is correct, you stop the conversation by saying [COMPLETE]. + If the answer is wrong, you ask student to fix it.", + llmConfig: new ConversableAgentConfig + { + Temperature = 0, + ConfigList = [gpt35], + }) + .RegisterMiddleware(async (msgs, option, agent, _) => + { + var reply = await agent.GenerateReplyAsync(msgs, option); + if (reply.GetContent()?.ToLower().Contains("complete") is true) + { + return new TextMessage(Role.Assistant, GroupChatExtension.TERMINATE, from: reply.From); + } + + return reply; + }) + .RegisterPrintMessage(); + + // create student agent + // student agent will answer the math questions + var student = new AssistantAgent( + name: "student", + systemMessage: "You are a student that answer question from teacher", + llmConfig: new ConversableAgentConfig + { + Temperature = 0, + ConfigList = [gpt35], + }) + .RegisterPrintMessage(); + + // start the conversation + var conversation = await student.InitiateChatAsync( + receiver: teacher, + message: "Hey teacher, please create math question for me.", + maxRound: 10); + + // output + // Message from teacher + // -------------------- + // content: Of course!Here's a math question for you: + // + // What is 2 + 3 ? + // -------------------- + // + // Message from student + // -------------------- + // content: The sum of 2 and 3 is 5. + // -------------------- + // + // Message from teacher + // -------------------- + // content: [GROUPCHAT_TERMINATE] + // -------------------- + #endregion code_snippet_1 + + conversation.Count().Should().BeLessThan(10); + conversation.Last().IsGroupChatTerminateMessage().Should().BeTrue(); + } +} diff --git a/dotnet/sample/AutoGen.BasicSamples/Example03_Agent_FunctionCall.cs b/dotnet/sample/AutoGen.BasicSamples/Example03_Agent_FunctionCall.cs new file mode 100644 index 00000000000..57b9ea76dcb --- /dev/null +++ b/dotnet/sample/AutoGen.BasicSamples/Example03_Agent_FunctionCall.cs @@ -0,0 +1,96 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// Example03_Agent_FunctionCall.cs + +using AutoGen; +using AutoGen.BasicSample; +using AutoGen.Core; +using FluentAssertions; + +/// +/// This example shows how to add type-safe function call to an agent. +/// +public partial class Example03_Agent_FunctionCall +{ + /// + /// upper case the message when asked. + /// + /// + [Function] + public async Task UpperCase(string message) + { + return message.ToUpper(); + } + + /// + /// Concatenate strings. + /// + /// strings to concatenate + [Function] + public async Task ConcatString(string[] strings) + { + return string.Join(" ", strings); + } + + /// + /// calculate tax + /// + /// price, should be an integer + /// tax rate, should be in range (0, 1) + [FunctionAttribute] + public async Task CalculateTax(int price, float taxRate) + { + return $"tax is {price * taxRate}"; + } + + public static async Task RunAsync() + { + var instance = new Example03_Agent_FunctionCall(); + var gpt35 = LLMConfiguration.GetAzureOpenAIGPT3_5_Turbo(); + + // AutoGen makes use of AutoGen.SourceGenerator to automatically generate FunctionDefinition and FunctionCallWrapper for you. + // The FunctionDefinition will be created based on function signature and XML documentation. + // The return type of type-safe function needs to be Task. And to get the best performance, please try only use primitive types and arrays of primitive types as parameters. + var config = new ConversableAgentConfig + { + Temperature = 0, + ConfigList = [gpt35], + FunctionContracts = new[] + { + instance.ConcatStringFunctionContract, + instance.UpperCaseFunctionContract, + instance.CalculateTaxFunctionContract, + }, + }; + + var agent = new AssistantAgent( + name: "agent", + systemMessage: "You are a helpful AI assistant", + llmConfig: config, + functionMap: new Dictionary>> + { + { nameof(ConcatString), instance.ConcatStringWrapper }, + { nameof(UpperCase), instance.UpperCaseWrapper }, + { nameof(CalculateTax), instance.CalculateTaxWrapper }, + }) + .RegisterPrintMessage(); + + // talk to the assistant agent + var upperCase = await agent.SendAsync("convert to upper case: hello world"); + upperCase.GetContent()?.Should().Be("HELLO WORLD"); + upperCase.Should().BeOfType>(); + upperCase.GetToolCalls().Should().HaveCount(1); + upperCase.GetToolCalls().First().FunctionName.Should().Be(nameof(UpperCase)); + + var concatString = await agent.SendAsync("concatenate strings: a, b, c, d, e"); + concatString.GetContent()?.Should().Be("a b c d e"); + concatString.Should().BeOfType>(); + concatString.GetToolCalls().Should().HaveCount(1); + concatString.GetToolCalls().First().FunctionName.Should().Be(nameof(ConcatString)); + + var calculateTax = await agent.SendAsync("calculate tax: 100, 0.1"); + calculateTax.GetContent().Should().Be("tax is 10"); + calculateTax.Should().BeOfType>(); + calculateTax.GetToolCalls().Should().HaveCount(1); + calculateTax.GetToolCalls().First().FunctionName.Should().Be(nameof(CalculateTax)); + } +} diff --git a/dotnet/sample/AutoGen.BasicSamples/Example04_Dynamic_GroupChat_Coding_Task.cs b/dotnet/sample/AutoGen.BasicSamples/Example04_Dynamic_GroupChat_Coding_Task.cs new file mode 100644 index 00000000000..c5d9a01f971 --- /dev/null +++ b/dotnet/sample/AutoGen.BasicSamples/Example04_Dynamic_GroupChat_Coding_Task.cs @@ -0,0 +1,263 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// Example04_Dynamic_GroupChat_Coding_Task.cs + +using AutoGen; +using AutoGen.BasicSample; +using AutoGen.Core; +using AutoGen.DotnetInteractive; +using AutoGen.OpenAI; +using FluentAssertions; + +public partial class Example04_Dynamic_GroupChat_Coding_Task +{ + public static async Task RunAsync() + { + var instance = new Example04_Dynamic_GroupChat_Coding_Task(); + + // setup dotnet interactive + var workDir = Path.Combine(Path.GetTempPath(), "InteractiveService"); + if (!Directory.Exists(workDir)) + Directory.CreateDirectory(workDir); + + using var service = new InteractiveService(workDir); + var dotnetInteractiveFunctions = new DotnetInteractiveFunction(service); + + var result = Path.Combine(workDir, "result.txt"); + if (File.Exists(result)) + File.Delete(result); + + await service.StartAsync(workDir, default); + + var gptConfig = LLMConfiguration.GetAzureOpenAIGPT3_5_Turbo(); + + var helperAgent = new GPTAgent( + name: "helper", + systemMessage: "You are a helpful AI assistant", + temperature: 0f, + config: gptConfig); + + var groupAdmin = new GPTAgent( + name: "groupAdmin", + systemMessage: "You are the admin of the group chat", + temperature: 0f, + config: gptConfig); + + var userProxy = new UserProxyAgent(name: "user", defaultReply: GroupChatExtension.TERMINATE, humanInputMode: HumanInputMode.NEVER) + .RegisterPrintMessage(); + + // Create admin agent + var admin = new AssistantAgent( + name: "admin", + systemMessage: """ + You are a manager who takes coding problem from user and resolve problem by splitting them into small tasks and assign each task to the most appropriate agent. + Here's available agents who you can assign task to: + - coder: write dotnet code to resolve task + - runner: run dotnet code from coder + + The workflow is as follows: + - You take the coding problem from user + - You break the problem into small tasks. For each tasks you first ask coder to write code to resolve the task. Once the code is written, you ask runner to run the code. + - Once a small task is resolved, you summarize the completed steps and create the next step. + - You repeat the above steps until the coding problem is resolved. + + You can use the following json format to assign task to agents: + ```task + { + "to": "{agent_name}", + "task": "{a short description of the task}", + "context": "{previous context from scratchpad}" + } + ``` + + If you need to ask user for extra information, you can use the following format: + ```ask + { + "question": "{question}" + } + ``` + + Once the coding problem is resolved, summarize each steps and results and send the summary to the user using the following format: + ```summary + { + "problem": "{coding problem}", + "steps": [ + { + "step": "{step}", + "result": "{result}" + } + ] + } + ``` + + Your reply must contain one of [task|ask|summary] to indicate the type of your message. + """, + llmConfig: new ConversableAgentConfig + { + Temperature = 0, + ConfigList = [gptConfig], + }) + .RegisterPrintMessage(); + + // create coder agent + // The coder agent is a composite agent that contains dotnet coder, code reviewer and nuget agent. + // The dotnet coder write dotnet code to resolve the task. + // The code reviewer review the code block from coder's reply. + // The nuget agent install nuget packages if there's any. + var coderAgent = new GPTAgent( + name: "coder", + systemMessage: @"You act as dotnet coder, you write dotnet code to resolve task. Once you finish writing code, ask runner to run the code for you. + +Here're some rules to follow on writing dotnet code: +- put code between ```csharp and ``` +- When creating http client, use `var httpClient = new HttpClient()`. Don't use `using var httpClient = new HttpClient()` because it will cause error when running the code. +- Try to use `var` instead of explicit type. +- Try avoid using external library, use .NET Core library instead. +- Use top level statement to write code. +- Always print out the result to console. Don't write code that doesn't print out anything. + +If you need to install nuget packages, put nuget packages in the following format: +```nuget +nuget_package_name +``` + +If your code is incorrect, Fix the error and send the code again. + +Here's some externel information +- The link to mlnet repo is: https://github.com/dotnet/machinelearning. you don't need a token to use github pr api. Make sure to include a User-Agent header, otherwise github will reject it. +", + config: gptConfig, + temperature: 0.4f) + .RegisterPrintMessage(); + + // code reviewer agent will review if code block from coder's reply satisfy the following conditions: + // - There's only one code block + // - The code block is csharp code block + // - The code block is top level statement + // - The code block is not using declaration + var codeReviewAgent = new GPTAgent( + name: "reviewer", + systemMessage: """ + You are a code reviewer who reviews code from coder. You need to check if the code satisfy the following conditions: + - The reply from coder contains at least one code block, e.g ```csharp and ``` + - There's only one code block and it's csharp code block + - The code block is not inside a main function. a.k.a top level statement + - The code block is not using declaration when creating http client + + You don't check the code style, only check if the code satisfy the above conditions. + + Put your comment between ```review and ```, if the code satisfies all conditions, put APPROVED in review.result field. Otherwise, put REJECTED along with comments. make sure your comment is clear and easy to understand. + + ## Example 1 ## + ```review + comment: The code satisfies all conditions. + result: APPROVED + ``` + + ## Example 2 ## + ```review + comment: The code is inside main function. Please rewrite the code in top level statement. + result: REJECTED + ``` + + """, + config: gptConfig, + temperature: 0f) + .RegisterPrintMessage(); + + // create runner agent + // The runner agent will run the code block from coder's reply. + // It runs dotnet code using dotnet interactive service hook. + // It also truncate the output if the output is too long. + var runner = new AssistantAgent( + name: "runner", + defaultReply: "No code available, coder, write code please") + .RegisterDotnetCodeBlockExectionHook(interactiveService: service) + .RegisterMiddleware(async (msgs, option, agent, ct) => + { + var mostRecentCoderMessage = msgs.LastOrDefault(x => x.From == "coder") ?? throw new Exception("No coder message found"); + return await agent.GenerateReplyAsync(new[] { mostRecentCoderMessage }, option, ct); + }) + .RegisterPrintMessage(); + + var adminToCoderTransition = Transition.Create(admin, coderAgent, async (from, to, messages) => + { + // the last message should be from admin + var lastMessage = messages.Last(); + if (lastMessage.From != admin.Name) + { + return false; + } + + return true; + }); + var coderToReviewerTransition = Transition.Create(coderAgent, codeReviewAgent); + var adminToRunnerTransition = Transition.Create(admin, runner, async (from, to, messages) => + { + // the last message should be from admin + var lastMessage = messages.Last(); + if (lastMessage.From != admin.Name) + { + return false; + } + + // the previous messages should contain a message from coder + var coderMessage = messages.FirstOrDefault(x => x.From == coderAgent.Name); + if (coderMessage is null) + { + return false; + } + + return true; + }); + + var runnerToAdminTransition = Transition.Create(runner, admin); + + var reviewerToAdminTransition = Transition.Create(codeReviewAgent, admin); + + var adminToUserTransition = Transition.Create(admin, userProxy, async (from, to, messages) => + { + // the last message should be from admin + var lastMessage = messages.Last(); + if (lastMessage.From != admin.Name) + { + return false; + } + + return true; + }); + + var userToAdminTransition = Transition.Create(userProxy, admin); + + var workflow = new Graph( + [ + adminToCoderTransition, + coderToReviewerTransition, + reviewerToAdminTransition, + adminToRunnerTransition, + runnerToAdminTransition, + adminToUserTransition, + userToAdminTransition, + ]); + + // create group chat + var groupChat = new GroupChat( + admin: groupAdmin, + members: [admin, coderAgent, runner, codeReviewAgent, userProxy], + workflow: workflow); + + // task 1: retrieve the most recent pr from mlnet and save it in result.txt + var groupChatManager = new GroupChatManager(groupChat); + await userProxy.SendAsync(groupChatManager, "Retrieve the most recent pr from mlnet and save it in result.txt", maxRound: 30); + File.Exists(result).Should().BeTrue(); + + // task 2: calculate the 39th fibonacci number + var answer = 63245986; + // clear the result file + File.Delete(result); + + var conversationHistory = await userProxy.InitiateChatAsync(groupChatManager, "What's the 39th of fibonacci number? Save the result in result.txt", maxRound: 10); + File.Exists(result).Should().BeTrue(); + var resultContent = File.ReadAllText(result); + resultContent.Should().Contain(answer.ToString()); + } +} diff --git a/dotnet/sample/AutoGen.BasicSamples/Example05_Dalle_And_GPT4V.cs b/dotnet/sample/AutoGen.BasicSamples/Example05_Dalle_And_GPT4V.cs new file mode 100644 index 00000000000..9fccd7ab385 --- /dev/null +++ b/dotnet/sample/AutoGen.BasicSamples/Example05_Dalle_And_GPT4V.cs @@ -0,0 +1,152 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// Example05_Dalle_And_GPT4V.cs + +using AutoGen; +using AutoGen.Core; +using Azure.AI.OpenAI; +using FluentAssertions; +using autogen = AutoGen.LLMConfigAPI; + +public partial class Example05_Dalle_And_GPT4V +{ + private readonly OpenAIClient openAIClient; + + public Example05_Dalle_And_GPT4V(OpenAIClient openAIClient) + { + this.openAIClient = openAIClient; + } + + /// + /// Generate image from prompt using DALL-E. + /// + /// prompt with feedback + /// + [Function] + public async Task GenerateImage(string prompt) + { + // TODO + // generate image from prompt using DALL-E + // and return url. + var option = new ImageGenerationOptions + { + Size = ImageSize.Size1024x1024, + Style = ImageGenerationStyle.Vivid, + ImageCount = 1, + Prompt = prompt, + Quality = ImageGenerationQuality.Standard, + DeploymentName = "dall-e-3", + }; + + var imageResponse = await openAIClient.GetImageGenerationsAsync(option); + var imageUrl = imageResponse.Value.Data.First().Url.OriginalString; + + return $@"// ignore this line [IMAGE_GENERATION] +The image is generated from prompt {prompt} + +{imageUrl}"; + } + + public static async Task RunAsync() + { + // This example shows how to use DALL-E and GPT-4V to generate image from prompt and feedback. + // The DALL-E agent will generate image from prompt. + // The GPT-4V agent will provide feedback to DALL-E agent to help it generate better image. + // The conversation will be terminated when the image satisfies the condition. + // The image will be saved to image.jpg in current directory. + + // get OpenAI Key and create config + var openAIKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new Exception("Please set OPENAI_API_KEY environment variable."); + var gpt35Config = autogen.GetOpenAIConfigList(openAIKey, new[] { "gpt-3.5-turbo" }); + var gpt4vConfig = autogen.GetOpenAIConfigList(openAIKey, new[] { "gpt-4-vision-preview" }); + var openAIClient = new OpenAIClient(openAIKey); + var instance = new Example05_Dalle_And_GPT4V(openAIClient); + var imagePath = Path.Combine(Environment.CurrentDirectory, "image.jpg"); + if (File.Exists(imagePath)) + { + File.Delete(imagePath); + } + + var dalleAgent = new AssistantAgent( + name: "dalle", + systemMessage: "You are a DALL-E agent that generate image from prompt, when conversation is terminated, return the most recent image url", + llmConfig: new ConversableAgentConfig + { + Temperature = 0, + ConfigList = gpt35Config, + FunctionContracts = new[] + { + instance.GenerateImageFunctionContract, + }, + }, + functionMap: new Dictionary>> + { + { nameof(GenerateImage), instance.GenerateImageWrapper }, + }) + .RegisterMiddleware(async (msgs, option, agent, ct) => + { + // if last message contains [TERMINATE], then find the last image url and terminate the conversation + if (msgs.Last().GetContent()?.Contains("TERMINATE") is true) + { + var lastMessageWithImage = msgs.Last(msg => msg is ImageMessage) as ImageMessage; + var lastImageUrl = lastMessageWithImage.Url; + Console.WriteLine($"download image from {lastImageUrl} to {imagePath}"); + var httpClient = new HttpClient(); + var imageBytes = await httpClient.GetByteArrayAsync(lastImageUrl); + File.WriteAllBytes(imagePath, imageBytes); + + var messageContent = $@"{GroupChatExtension.TERMINATE} + +{lastImageUrl}"; + return new TextMessage(Role.Assistant, messageContent) + { + From = "dalle", + }; + } + + var reply = await agent.GenerateReplyAsync(msgs, option, ct); + + if (reply.GetContent() is string content && content.Contains("IMAGE_GENERATION")) + { + var imageUrl = content.Split("\n").Last(); + var imageMessage = new ImageMessage(Role.Assistant, imageUrl, from: reply.From); + + return imageMessage; + } + else + { + return reply; + } + }) + .RegisterPrintMessage(); + + var gpt4VAgent = new AssistantAgent( + name: "gpt4v", + systemMessage: @"You are a critism that provide feedback to DALL-E agent. +Carefully check the image generated by DALL-E agent and provide feedback. +If the image satisfies the condition, then terminate the conversation by saying [TERMINATE]. +Otherwise, provide detailed feedback to DALL-E agent so it can generate better image. + +The image should satisfy the following conditions: +- There should be a cat and a mouse in the image +- The cat should be chasing after the mouse +", + llmConfig: new ConversableAgentConfig + { + Temperature = 0, + ConfigList = gpt4vConfig, + }) + .RegisterPrintMessage(); + + IEnumerable conversation = new List() + { + new TextMessage(Role.User, "Hey dalle, please generate image from prompt: English short hair blue cat chase after a mouse") + }; + var maxRound = 20; + await gpt4VAgent.InitiateChatAsync( + receiver: dalleAgent, + message: "Hey dalle, please generate image from prompt: English short hair blue cat chase after a mouse", + maxRound: maxRound); + + File.Exists(imagePath).Should().BeTrue(); + } +} diff --git a/dotnet/sample/AutoGen.BasicSamples/Example06_UserProxyAgent.cs b/dotnet/sample/AutoGen.BasicSamples/Example06_UserProxyAgent.cs new file mode 100644 index 00000000000..dd3b5a67192 --- /dev/null +++ b/dotnet/sample/AutoGen.BasicSamples/Example06_UserProxyAgent.cs @@ -0,0 +1,32 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// Example06_UserProxyAgent.cs +using AutoGen.Core; +using AutoGen.OpenAI; + +namespace AutoGen.BasicSample; + +public static class Example06_UserProxyAgent +{ + public static async Task RunAsync() + { + var gpt35 = LLMConfiguration.GetOpenAIGPT3_5_Turbo(); + + var assistantAgent = new GPTAgent( + name: "assistant", + systemMessage: "You are an assistant that help user to do some tasks.", + config: gpt35) + .RegisterPrintMessage(); + + // set human input mode to ALWAYS so that user always provide input + var userProxyAgent = new UserProxyAgent( + name: "user", + humanInputMode: HumanInputMode.ALWAYS) + .RegisterPrintMessage(); + + // start the conversation + await userProxyAgent.InitiateChatAsync( + receiver: assistantAgent, + message: "Hey assistant, please help me to do some tasks.", + maxRound: 10); + } +} diff --git a/dotnet/sample/AutoGen.BasicSamples/Example07_Dynamic_GroupChat_Calculate_Fibonacci.cs b/dotnet/sample/AutoGen.BasicSamples/Example07_Dynamic_GroupChat_Calculate_Fibonacci.cs new file mode 100644 index 00000000000..6584baa5fae --- /dev/null +++ b/dotnet/sample/AutoGen.BasicSamples/Example07_Dynamic_GroupChat_Calculate_Fibonacci.cs @@ -0,0 +1,368 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// Example07_Dynamic_GroupChat_Calculate_Fibonacci.cs + +using System.Text; +using System.Text.Json; +using AutoGen; +using AutoGen.BasicSample; +using AutoGen.Core; +using AutoGen.DotnetInteractive; +using AutoGen.OpenAI; +using FluentAssertions; + +public partial class Example07_Dynamic_GroupChat_Calculate_Fibonacci +{ + #region reviewer_function + public struct CodeReviewResult + { + public bool HasMultipleCodeBlocks { get; set; } + public bool IsTopLevelStatement { get; set; } + public bool IsDotnetCodeBlock { get; set; } + public bool IsPrintResultToConsole { get; set; } + } + + /// + /// review code block + /// + /// true if there're multipe csharp code blocks + /// true if the code is in top level statement + /// true if the code block is csharp code block + /// true if the code block print out result to console + [Function] + public async Task ReviewCodeBlock( + bool hasMultipleCodeBlocks, + bool isTopLevelStatement, + bool isDotnetCodeBlock, + bool isPrintResultToConsole) + { + var obj = new CodeReviewResult + { + HasMultipleCodeBlocks = hasMultipleCodeBlocks, + IsTopLevelStatement = isTopLevelStatement, + IsDotnetCodeBlock = isDotnetCodeBlock, + IsPrintResultToConsole = isPrintResultToConsole, + }; + + return JsonSerializer.Serialize(obj); + } + #endregion reviewer_function + + #region create_coder + public static async Task CreateCoderAgentAsync() + { + var gpt3Config = LLMConfiguration.GetAzureOpenAIGPT3_5_Turbo(); + var coder = new GPTAgent( + name: "coder", + systemMessage: @"You act as dotnet coder, you write dotnet code to resolve task. Once you finish writing code, ask runner to run the code for you. + + Here're some rules to follow on writing dotnet code: + - put code between ```csharp and ``` + - Avoid adding `using` keyword when creating disposable object. e.g `var httpClient = new HttpClient()` + - Try to use `var` instead of explicit type. + - Try avoid using external library, use .NET Core library instead. + - Use top level statement to write code. + - Always print out the result to console. Don't write code that doesn't print out anything. + + If you need to install nuget packages, put nuget packages in the following format: + ```nuget + nuget_package_name + ``` + + If your code is incorrect, runner will tell you the error message. Fix the error and send the code again.", + config: gpt3Config, + temperature: 0.4f) + .RegisterPrintMessage(); + + return coder; + } + #endregion create_coder + + #region create_runner + public static async Task CreateRunnerAgentAsync(InteractiveService service) + { + var runner = new AssistantAgent( + name: "runner", + systemMessage: "You run dotnet code", + defaultReply: "No code available.") + .RegisterDotnetCodeBlockExectionHook(interactiveService: service) + .RegisterMiddleware(async (msgs, option, agent, _) => + { + if (msgs.Count() == 0 || msgs.All(msg => msg.From != "coder")) + { + return new TextMessage(Role.Assistant, "No code available. Coder please write code"); + } + else + { + var coderMsg = msgs.Last(msg => msg.From == "coder"); + return await agent.GenerateReplyAsync([coderMsg], option); + } + }) + .RegisterPrintMessage(); + + return runner; + } + #endregion create_runner + + #region create_admin + public static async Task CreateAdminAsync() + { + var gpt3Config = LLMConfiguration.GetAzureOpenAIGPT3_5_Turbo(); + var admin = new GPTAgent( + name: "admin", + systemMessage: "You are group admin, terminate the group chat once task is completed by saying [TERMINATE] plus the final answer", + temperature: 0, + config: gpt3Config) + .RegisterMiddleware(async (msgs, option, agent, _) => + { + var reply = await agent.GenerateReplyAsync(msgs, option); + if (reply is TextMessage textMessage && textMessage.Content.Contains("TERMINATE") is true) + { + var content = $"{textMessage.Content}\n\n {GroupChatExtension.TERMINATE}"; + + return new TextMessage(Role.Assistant, content, from: reply.From); + } + + return reply; + }); + + return admin; + } + #endregion create_admin + + #region create_reviewer + public static async Task CreateReviewerAgentAsync() + { + var gpt3Config = LLMConfiguration.GetAzureOpenAIGPT3_5_Turbo(); + var functions = new Example07_Dynamic_GroupChat_Calculate_Fibonacci(); + var reviewer = new GPTAgent( + name: "code_reviewer", + systemMessage: @"You review code block from coder", + config: gpt3Config, + functions: [functions.ReviewCodeBlockFunction], + functionMap: new Dictionary>>() + { + { nameof(ReviewCodeBlock), functions.ReviewCodeBlockWrapper }, + }) + .RegisterMiddleware(async (msgs, option, innerAgent, ct) => + { + var maxRetry = 3; + var reply = await innerAgent.GenerateReplyAsync(msgs, option, ct); + while (maxRetry-- > 0) + { + if (reply.GetToolCalls() is var toolCalls && toolCalls.Count() == 1 && toolCalls[0].FunctionName == nameof(ReviewCodeBlock)) + { + var toolCallResult = reply.GetContent(); + var reviewResultObj = JsonSerializer.Deserialize(toolCallResult); + var reviews = new List(); + if (reviewResultObj.HasMultipleCodeBlocks) + { + var fixCodeBlockPrompt = @"There're multiple code blocks, please combine them into one code block"; + reviews.Add(fixCodeBlockPrompt); + } + + if (reviewResultObj.IsDotnetCodeBlock is false) + { + var fixCodeBlockPrompt = @"The code block is not csharp code block, please write dotnet code only"; + reviews.Add(fixCodeBlockPrompt); + } + + if (reviewResultObj.IsTopLevelStatement is false) + { + var fixCodeBlockPrompt = @"The code is not top level statement, please rewrite your dotnet code using top level statement"; + reviews.Add(fixCodeBlockPrompt); + } + + if (reviewResultObj.IsPrintResultToConsole is false) + { + var fixCodeBlockPrompt = @"The code doesn't print out result to console, please print out result to console"; + reviews.Add(fixCodeBlockPrompt); + } + + if (reviews.Count > 0) + { + var sb = new StringBuilder(); + sb.AppendLine("There're some comments from code reviewer, please fix these comments"); + foreach (var review in reviews) + { + sb.AppendLine($"- {review}"); + } + + return new TextMessage(Role.Assistant, sb.ToString(), from: "code_reviewer"); + } + else + { + var msg = new TextMessage(Role.Assistant, "The code looks good, please ask runner to run the code for you.") + { + From = "code_reviewer", + }; + + return msg; + } + } + else + { + var originalContent = reply.GetContent(); + var prompt = $@"Please convert the content to ReviewCodeBlock function arguments. + + ## Original Content + {originalContent}"; + + reply = await innerAgent.SendAsync(prompt, msgs, ct); + } + } + + throw new Exception("Failed to review code block"); + }) + .RegisterPrintMessage(); + + return reviewer; + } + #endregion create_reviewer + + public static async Task RunWorkflowAsync() + { + long the39thFibonacciNumber = 63245986; + var workDir = Path.Combine(Path.GetTempPath(), "InteractiveService"); + if (!Directory.Exists(workDir)) + Directory.CreateDirectory(workDir); + + using var service = new InteractiveService(workDir); + var dotnetInteractiveFunctions = new DotnetInteractiveFunction(service); + + await service.StartAsync(workDir, default); + + #region create_workflow + var reviewer = await CreateReviewerAgentAsync(); + var coder = await CreateCoderAgentAsync(); + var runner = await CreateRunnerAgentAsync(service); + var admin = await CreateAdminAsync(); + + var admin2CoderTransition = Transition.Create(admin, coder); + var coder2ReviewerTransition = Transition.Create(coder, reviewer); + var reviewer2RunnerTransition = Transition.Create( + from: reviewer, + to: runner, + canTransitionAsync: async (from, to, messages) => + { + var lastMessage = messages.Last(); + if (lastMessage is TextMessage textMessage && textMessage.Content.ToLower().Contains("the code looks good, please ask runner to run the code for you.") is true) + { + // ask runner to run the code + return true; + } + + return false; + }); + var reviewer2CoderTransition = Transition.Create( + from: reviewer, + to: coder, + canTransitionAsync: async (from, to, messages) => + { + var lastMessage = messages.Last(); + if (lastMessage is TextMessage textMessage && textMessage.Content.ToLower().Contains("there're some comments from code reviewer, please fix these comments") is true) + { + // ask coder to fix the code based on reviewer's comments + return true; + } + + return false; + }); + + var runner2CoderTransition = Transition.Create( + from: runner, + to: coder, + canTransitionAsync: async (from, to, messages) => + { + var lastMessage = messages.Last(); + if (lastMessage is TextMessage textMessage && textMessage.Content.ToLower().Contains("error") is true) + { + // ask coder to fix the error + return true; + } + + return false; + }); + var runner2AdminTransition = Transition.Create(runner, admin); + + var workflow = new Graph( + [ + admin2CoderTransition, + coder2ReviewerTransition, + reviewer2RunnerTransition, + reviewer2CoderTransition, + runner2CoderTransition, + runner2AdminTransition, + ]); + #endregion create_workflow + + #region create_group_chat_with_workflow + var groupChat = new GroupChat( + admin: admin, + workflow: workflow, + members: + [ + admin, + coder, + runner, + reviewer, + ]); + + admin.SendIntroduction("Welcome to my group, work together to resolve my task", groupChat); + coder.SendIntroduction("I will write dotnet code to resolve task", groupChat); + reviewer.SendIntroduction("I will review dotnet code", groupChat); + runner.SendIntroduction("I will run dotnet code once the review is done", groupChat); + + var groupChatManager = new GroupChatManager(groupChat); + var conversationHistory = await admin.InitiateChatAsync(groupChatManager, "What's the 39th of fibonacci number?", maxRound: 10); + #endregion create_group_chat_with_workflow + // the last message is from admin, which is the termination message + var lastMessage = conversationHistory.Last(); + lastMessage.From.Should().Be("admin"); + lastMessage.IsGroupChatTerminateMessage().Should().BeTrue(); + lastMessage.Should().BeOfType(); + lastMessage.GetContent().Should().Contain(the39thFibonacciNumber.ToString()); + } + + public static async Task RunAsync() + { + long the39thFibonacciNumber = 63245986; + var workDir = Path.Combine(Path.GetTempPath(), "InteractiveService"); + if (!Directory.Exists(workDir)) + Directory.CreateDirectory(workDir); + + using var service = new InteractiveService(workDir); + var dotnetInteractiveFunctions = new DotnetInteractiveFunction(service); + + await service.StartAsync(workDir, default); + #region create_group_chat + var reviewer = await CreateReviewerAgentAsync(); + var coder = await CreateCoderAgentAsync(); + var runner = await CreateRunnerAgentAsync(service); + var admin = await CreateAdminAsync(); + var groupChat = new GroupChat( + admin: admin, + members: + [ + admin, + coder, + runner, + reviewer, + ]); + + admin.SendIntroduction("Welcome to my group, work together to resolve my task", groupChat); + coder.SendIntroduction("I will write dotnet code to resolve task", groupChat); + reviewer.SendIntroduction("I will review dotnet code", groupChat); + runner.SendIntroduction("I will run dotnet code once the review is done", groupChat); + + var groupChatManager = new GroupChatManager(groupChat); + var conversationHistory = await admin.InitiateChatAsync(groupChatManager, "What's the 39th of fibonacci number?", maxRound: 10); + + // the last message is from admin, which is the termination message + var lastMessage = conversationHistory.Last(); + lastMessage.From.Should().Be("admin"); + lastMessage.IsGroupChatTerminateMessage().Should().BeTrue(); + lastMessage.Should().BeOfType(); + lastMessage.GetContent().Should().Contain(the39thFibonacciNumber.ToString()); + #endregion create_group_chat + } +} diff --git a/dotnet/sample/AutoGen.BasicSamples/Example08_LMStudio.cs b/dotnet/sample/AutoGen.BasicSamples/Example08_LMStudio.cs new file mode 100644 index 00000000000..cce33011762 --- /dev/null +++ b/dotnet/sample/AutoGen.BasicSamples/Example08_LMStudio.cs @@ -0,0 +1,44 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// Example08_LMStudio.cs + +#region lmstudio_using_statements +using AutoGen.Core; +using AutoGen.LMStudio; +#endregion lmstudio_using_statements + +namespace AutoGen.BasicSample; + +public class Example08_LMStudio +{ + public static async Task RunAsync() + { + #region lmstudio_example_1 + var config = new LMStudioConfig("localhost", 1234); + var lmAgent = new LMStudioAgent("asssistant", config: config) + .RegisterPrintMessage(); + + await lmAgent.SendAsync("Can you write a piece of C# code to calculate 100th of fibonacci?"); + + // output from assistant (the output below is generated using llama-2-chat-7b, the output may vary depending on the model used) + // + // Of course! To calculate the 100th number in the Fibonacci sequence using C#, you can use the following code:``` + // using System; + // class FibonacciSequence { + // static int Fibonacci(int n) { + // if (n <= 1) { + // return 1; + // } else { + // return Fibonacci(n - 1) + Fibonacci(n - 2); + // } + // } + // static void Main() { + // Console.WriteLine("The 100th number in the Fibonacci sequence is: " + Fibonacci(100)); + // } + // } + // ``` + // In this code, we define a function `Fibonacci` that takes an integer `n` as input and returns the `n`-th number in the Fibonacci sequence. The function uses a recursive approach to calculate the value of the sequence. + // The `Main` method simply calls the `Fibonacci` function with the argument `100`, and prints the result to the console. + // Note that this code will only work for positive integers `n`. If you want to calculate the Fibonacci sequence for other types of numbers, such as real or complex numbers, you will need to modify the code accordingly. + #endregion lmstudio_example_1 + } +} diff --git a/dotnet/sample/AutoGen.BasicSamples/Example09_LMStudio_FunctionCall.cs b/dotnet/sample/AutoGen.BasicSamples/Example09_LMStudio_FunctionCall.cs new file mode 100644 index 00000000000..9a62144df2b --- /dev/null +++ b/dotnet/sample/AutoGen.BasicSamples/Example09_LMStudio_FunctionCall.cs @@ -0,0 +1,135 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// Example09_LMStudio_FunctionCall.cs + +using System.Text.Json; +using System.Text.Json.Serialization; +using AutoGen.Core; +using AutoGen.LMStudio; +using Azure.AI.OpenAI; + +namespace AutoGen.BasicSample; + +public class LLaMAFunctionCall +{ + [JsonPropertyName("name")] + public string Name { get; set; } + + [JsonPropertyName("arguments")] + public JsonElement Arguments { get; set; } +} + +public partial class Example09_LMStudio_FunctionCall +{ + /// + /// Get weather from location. + /// + /// location + /// date. type is string + [Function] + public async Task GetWeather(string location, string date) + { + return $"[Function] The weather on {date} in {location} is sunny."; + } + + + /// + /// Search query on Google and return the results. + /// + /// search query + [Function] + public async Task GoogleSearch(string query) + { + return $"[Function] Here are the search results for {query}."; + } + + private static object SerializeFunctionDefinition(FunctionDefinition functionDefinition) + { + return new + { + type = "function", + function = new + { + name = functionDefinition.Name, + description = functionDefinition.Description, + parameters = functionDefinition.Parameters.ToObjectFromJson(), + } + }; + } + + public static async Task RunAsync() + { + #region lmstudio_function_call_example + // This example has been verified to work with Trelis-Llama-2-7b-chat-hf-function-calling-v3 + var instance = new Example09_LMStudio_FunctionCall(); + var config = new LMStudioConfig("localhost", 1234); + var systemMessage = @$"You are a helpful AI assistant."; + + // Because the LM studio server doesn't support openai function call yet + // To simulate the function call, we can put the function call details in the system message + // And ask agent to response in function call object format using few-shot example + object[] functionList = + [ + SerializeFunctionDefinition(instance.GetWeatherFunction), + SerializeFunctionDefinition(instance.GoogleSearchFunction) + ]; + var functionListString = JsonSerializer.Serialize(functionList, new JsonSerializerOptions { WriteIndented = true }); + var lmAgent = new LMStudioAgent( + name: "assistant", + systemMessage: @$" +You are a helpful AI assistant +You have access to the following functions. Use them if required: + +{functionListString}", + config: config) + .RegisterMiddleware(async (msgs, option, innerAgent, ct) => + { + // inject few-shot example to the message + var exampleGetWeather = new TextMessage(Role.User, "Get weather in London"); + var exampleAnswer = new TextMessage(Role.Assistant, "{\n \"name\": \"GetWeather\",\n \"arguments\": {\n \"city\": \"London\"\n }\n}", from: innerAgent.Name); + + msgs = new[] { exampleGetWeather, exampleAnswer }.Concat(msgs).ToArray(); + var reply = await innerAgent.GenerateReplyAsync(msgs, option, ct); + + // if reply is a function call, invoke function + var content = reply.GetContent(); + try + { + if (JsonSerializer.Deserialize(content) is { } functionCall) + { + var arguments = JsonSerializer.Serialize(functionCall.Arguments); + // invoke function wrapper + if (functionCall.Name == instance.GetWeatherFunction.Name) + { + var result = await instance.GetWeatherWrapper(arguments); + return new TextMessage(Role.Assistant, result); + } + else if (functionCall.Name == instance.GoogleSearchFunction.Name) + { + var result = await instance.GoogleSearchWrapper(arguments); + return new TextMessage(Role.Assistant, result); + } + else + { + throw new Exception($"Unknown function call: {functionCall.Name}"); + } + } + } + catch (JsonException) + { + // ignore + } + + return reply; + }) + .RegisterPrintMessage(); + + var userProxyAgent = new UserProxyAgent( + name: "user", + humanInputMode: HumanInputMode.ALWAYS); + + await userProxyAgent.SendAsync( + receiver: lmAgent, + "Search the names of the five largest stocks in the US by market cap "); + #endregion lmstudio_function_call_example + } +} diff --git a/dotnet/sample/AutoGen.BasicSamples/Example10_SemanticKernel.cs b/dotnet/sample/AutoGen.BasicSamples/Example10_SemanticKernel.cs new file mode 100644 index 00000000000..61c341204ec --- /dev/null +++ b/dotnet/sample/AutoGen.BasicSamples/Example10_SemanticKernel.cs @@ -0,0 +1,80 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// Example10_SemanticKernel.cs + +using System.ComponentModel; +using AutoGen.Core; +using AutoGen.SemanticKernel.Extension; +using FluentAssertions; +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.ChatCompletion; +using Microsoft.SemanticKernel.Connectors.OpenAI; +namespace AutoGen.BasicSample; + +public class LightPlugin +{ + public bool IsOn { get; set; } = false; + + [KernelFunction] + [Description("Gets the state of the light.")] + public string GetState() => this.IsOn ? "on" : "off"; + + [KernelFunction] + [Description("Changes the state of the light.'")] + public string ChangeState(bool newState) + { + this.IsOn = newState; + var state = this.GetState(); + + // Print the state to the console + Console.ForegroundColor = ConsoleColor.DarkBlue; + Console.WriteLine($"[Light is now {state}]"); + Console.ResetColor(); + + return state; + } +} + +public class Example10_SemanticKernel +{ + public static async Task RunAsync() + { + var openAIKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new Exception("Please set OPENAI_API_KEY environment variable."); + var modelId = "gpt-3.5-turbo"; + var builder = Kernel.CreateBuilder() + .AddOpenAIChatCompletion(modelId: modelId, apiKey: openAIKey); + var kernel = builder.Build(); + var settings = new OpenAIPromptExecutionSettings + { + ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions, + }; + + kernel.Plugins.AddFromObject(new LightPlugin()); + var skAgent = kernel + .ToSemanticKernelAgent(name: "assistant", systemMessage: "You control the light", settings); + + // Send a message to the skAgent, the skAgent supports the following message types: + // - IMessage + // - (streaming) IMessage + // You can create an IMessage using MessageEnvelope.Create + var chatMessageContent = MessageEnvelope.Create(new ChatMessageContent(AuthorRole.User, "Toggle the light")); + var reply = await skAgent.SendAsync(chatMessageContent); + reply.Should().BeOfType>(); + Console.WriteLine((reply as IMessage).Content.Items[0].As().Text); + + var skAgentWithMiddleware = skAgent + .RegisterMessageConnector() // Register the message connector to support more AutoGen built-in message types + .RegisterPrintMessage(); + + // Now the skAgentWithMiddleware supports more IMessage types like TextMessage, ImageMessage or MultiModalMessage + // It also register a print format message hook to print the message in a human readable format to the console + await skAgent.SendAsync(chatMessageContent); + await skAgentWithMiddleware.SendAsync(new TextMessage(Role.User, "Toggle the light")); + + // The more message type an agent support, the more flexible it is to be used in different scenarios + // For example, since the TextMessage is supported, the skAgentWithMiddleware can be used with user proxy. + var userProxy = new UserProxyAgent("user"); + + await skAgentWithMiddleware.InitiateChatAsync(userProxy, "how can I help you today"); + } + +} diff --git a/dotnet/sample/AutoGen.BasicSamples/Example11_Sequential_GroupChat_Example.cs b/dotnet/sample/AutoGen.BasicSamples/Example11_Sequential_GroupChat_Example.cs new file mode 100644 index 00000000000..00ff321082a --- /dev/null +++ b/dotnet/sample/AutoGen.BasicSamples/Example11_Sequential_GroupChat_Example.cs @@ -0,0 +1,94 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// Example11_Sequential_GroupChat_Example.cs + +#region using_statement +using AutoGen.Core; +using AutoGen.OpenAI; +using AutoGen.OpenAI.Extension; +using AutoGen.SemanticKernel; +using AutoGen.SemanticKernel.Extension; +using Azure.AI.OpenAI; +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.Plugins.Web; +using Microsoft.SemanticKernel.Plugins.Web.Bing; +#endregion using_statement + +namespace AutoGen.BasicSample; + +public partial class Sequential_GroupChat_Example +{ + public static async Task CreateBingSearchAgentAsync() + { + #region CreateBingSearchAgent + var config = LLMConfiguration.GetAzureOpenAIGPT3_5_Turbo(); + var apiKey = config.ApiKey; + var kernelBuilder = Kernel.CreateBuilder() + .AddAzureOpenAIChatCompletion(config.DeploymentName, config.Endpoint, apiKey); + var bingApiKey = Environment.GetEnvironmentVariable("BING_API_KEY") ?? throw new Exception("BING_API_KEY environment variable is not set"); + var bingSearch = new BingConnector(bingApiKey); + var webSearchPlugin = new WebSearchEnginePlugin(bingSearch); + kernelBuilder.Plugins.AddFromObject(webSearchPlugin); + + var kernel = kernelBuilder.Build(); + var kernelAgent = new SemanticKernelAgent( + kernel: kernel, + name: "bing-search", + systemMessage: """ + You search results from Bing and return it as-is. + You put the original search result between ```bing and ``` + + e.g. + ```bing + xxx + ``` + """) + .RegisterMessageConnector() + .RegisterPrintMessage(); // pretty print the message + + return kernelAgent; + #endregion CreateBingSearchAgent + } + + public static async Task CreateSummarizerAgentAsync() + { + #region CreateSummarizerAgent + var config = LLMConfiguration.GetAzureOpenAIGPT3_5_Turbo(); + var apiKey = config.ApiKey; + var endPoint = new Uri(config.Endpoint); + + var openAIClient = new OpenAIClient(endPoint, new Azure.AzureKeyCredential(apiKey)); + var openAIClientAgent = new OpenAIChatAgent( + openAIClient: openAIClient, + name: "summarizer", + modelName: config.DeploymentName, + systemMessage: "You summarize search result from bing in a short and concise manner"); + + return openAIClientAgent + .RegisterMessageConnector() + .RegisterPrintMessage(); // pretty print the message + #endregion CreateSummarizerAgent + } + + public static async Task RunAsync() + { + #region Sequential_GroupChat_Example + var userProxyAgent = new UserProxyAgent( + name: "user", + humanInputMode: HumanInputMode.ALWAYS) + .RegisterPrintMessage(); + + var bingSearchAgent = await CreateBingSearchAgentAsync(); + var summarizerAgent = await CreateSummarizerAgentAsync(); + + var groupChat = new RoundRobinGroupChat( + agents: [userProxyAgent, bingSearchAgent, summarizerAgent]); + + var groupChatAgent = new GroupChatManager(groupChat); + + var history = await userProxyAgent.InitiateChatAsync( + receiver: groupChatAgent, + message: "How to deploy an openai resource on azure", + maxRound: 10); + #endregion Sequential_GroupChat_Example + } +} diff --git a/dotnet/sample/AutoGen.BasicSamples/Example12_TwoAgent_Fill_Application.cs b/dotnet/sample/AutoGen.BasicSamples/Example12_TwoAgent_Fill_Application.cs new file mode 100644 index 00000000000..b622a3e641e --- /dev/null +++ b/dotnet/sample/AutoGen.BasicSamples/Example12_TwoAgent_Fill_Application.cs @@ -0,0 +1,199 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// Example12_TwoAgent_Fill_Application.cs + +using System.Text; +using AutoGen.Core; +using AutoGen.OpenAI; +using AutoGen.OpenAI.Extension; +using Azure.AI.OpenAI; + +namespace AutoGen.BasicSample; + +public partial class TwoAgent_Fill_Application +{ + private string? name = null; + private string? email = null; + private string? phone = null; + private string? address = null; + private bool? receiveUpdates = null; + + [Function] + public async Task SaveProgress( + string name, + string email, + string phone, + string address, + bool? receiveUpdates) + { + this.name = !string.IsNullOrEmpty(name) ? name : this.name; + this.email = !string.IsNullOrEmpty(email) ? email : this.email; + this.phone = !string.IsNullOrEmpty(phone) ? phone : this.phone; + this.address = !string.IsNullOrEmpty(address) ? address : this.address; + this.receiveUpdates = receiveUpdates ?? this.receiveUpdates; + + var missingInformationStringBuilder = new StringBuilder(); + if (string.IsNullOrEmpty(this.name)) + { + missingInformationStringBuilder.AppendLine("Name is missing."); + } + + if (string.IsNullOrEmpty(this.email)) + { + missingInformationStringBuilder.AppendLine("Email is missing."); + } + + if (string.IsNullOrEmpty(this.phone)) + { + missingInformationStringBuilder.AppendLine("Phone is missing."); + } + + if (string.IsNullOrEmpty(this.address)) + { + missingInformationStringBuilder.AppendLine("Address is missing."); + } + + if (this.receiveUpdates == null) + { + missingInformationStringBuilder.AppendLine("ReceiveUpdates is missing."); + } + + if (missingInformationStringBuilder.Length > 0) + { + return missingInformationStringBuilder.ToString(); + } + else + { + return "Application information is saved to database."; + } + } + + public static async Task CreateSaveProgressAgent() + { + var gpt3Config = LLMConfiguration.GetAzureOpenAIGPT3_5_Turbo(); + var endPoint = gpt3Config.Endpoint ?? throw new Exception("Please set AZURE_OPENAI_ENDPOINT environment variable."); + var apiKey = gpt3Config.ApiKey ?? throw new Exception("Please set AZURE_OPENAI_API_KEY environment variable."); + var openaiClient = new OpenAIClient(new Uri(endPoint), new Azure.AzureKeyCredential(apiKey)); + + var instance = new TwoAgent_Fill_Application(); + var functionCallConnector = new FunctionCallMiddleware( + functions: [instance.SaveProgressFunctionContract], + functionMap: new Dictionary>> + { + { instance.SaveProgressFunctionContract.Name, instance.SaveProgressWrapper }, + }); + + var chatAgent = new OpenAIChatAgent( + openAIClient: openaiClient, + name: "application", + modelName: gpt3Config.DeploymentName, + systemMessage: """You are a helpful application form assistant who saves progress while user fills application.""") + .RegisterMessageConnector() + .RegisterMiddleware(functionCallConnector) + .RegisterMiddleware(async (msgs, option, agent, ct) => + { + var lastUserMessage = msgs.Last() ?? throw new Exception("No user message found."); + var prompt = $""" + Save progress according to the most recent information provided by user. + + ```user + {lastUserMessage.GetContent()} + ``` + """; + + return await agent.GenerateReplyAsync([lastUserMessage], option, ct); + + }); + + return chatAgent; + } + + public static async Task CreateAssistantAgent() + { + var gpt3Config = LLMConfiguration.GetAzureOpenAIGPT3_5_Turbo(); + var endPoint = gpt3Config.Endpoint ?? throw new Exception("Please set AZURE_OPENAI_ENDPOINT environment variable."); + var apiKey = gpt3Config.ApiKey ?? throw new Exception("Please set AZURE_OPENAI_API_KEY environment variable."); + var openaiClient = new OpenAIClient(new Uri(endPoint), new Azure.AzureKeyCredential(apiKey)); + + var chatAgent = new OpenAIChatAgent( + openAIClient: openaiClient, + name: "assistant", + modelName: gpt3Config.DeploymentName, + systemMessage: """You create polite prompt to ask user provide missing information""") + .RegisterMessageConnector() + .RegisterPrintMessage() + .RegisterMiddleware(async (msgs, option, agent, ct) => + { + var lastReply = msgs.Last() ?? throw new Exception("No reply found."); + var reply = await agent.GenerateReplyAsync(msgs, option, ct); + + // if application is complete, exit conversation by sending termination message + if (lastReply.GetContent().Contains("Application information is saved to database.")) + { + return new TextMessage(Role.Assistant, GroupChatExtension.TERMINATE, from: agent.Name); + } + else + { + return reply; + } + }); + + return chatAgent; + } + + public static async Task CreateUserAgent() + { + var gpt3Config = LLMConfiguration.GetAzureOpenAIGPT3_5_Turbo(); + var endPoint = gpt3Config.Endpoint ?? throw new Exception("Please set AZURE_OPENAI_ENDPOINT environment variable."); + var apiKey = gpt3Config.ApiKey ?? throw new Exception("Please set AZURE_OPENAI_API_KEY environment variable."); + var openaiClient = new OpenAIClient(new Uri(endPoint), new Azure.AzureKeyCredential(apiKey)); + + var chatAgent = new OpenAIChatAgent( + openAIClient: openaiClient, + name: "user", + modelName: gpt3Config.DeploymentName, + systemMessage: """ + You are a user who is filling an application form. Simply provide the information as requested and answer the questions, don't do anything else. + + here's some personal information about you: + - name: John Doe + - email: 1234567@gmail.com + - phone: 123-456-7890 + - address: 1234 Main St, Redmond, WA 98052 + - want to receive update? true + """) + .RegisterMessageConnector() + .RegisterPrintMessage(); + + return chatAgent; + } + + public static async Task RunAsync() + { + var applicationAgent = await CreateSaveProgressAgent(); + var assistantAgent = await CreateAssistantAgent(); + var userAgent = await CreateUserAgent(); + + var userToApplicationTransition = Transition.Create(userAgent, applicationAgent); + var applicationToAssistantTransition = Transition.Create(applicationAgent, assistantAgent); + var assistantToUserTransition = Transition.Create(assistantAgent, userAgent); + + var workflow = new Graph( + [ + userToApplicationTransition, + applicationToAssistantTransition, + assistantToUserTransition, + ]); + + var groupChat = new GroupChat( + members: [userAgent, applicationAgent, assistantAgent], + workflow: workflow); + + var groupChatManager = new GroupChatManager(groupChat); + var initialMessage = await assistantAgent.SendAsync("Generate a greeting meesage for user and start the conversation by asking what's their name."); + + var chatHistory = await userAgent.SendAsync(groupChatManager, [initialMessage], maxRound: 30); + + var lastMessage = chatHistory.Last(); + Console.WriteLine(lastMessage.GetContent()); + } +} diff --git a/dotnet/sample/AutoGen.BasicSamples/Example13_OpenAIAgent_JsonMode.cs b/dotnet/sample/AutoGen.BasicSamples/Example13_OpenAIAgent_JsonMode.cs new file mode 100644 index 00000000000..35b7b7d1d2f --- /dev/null +++ b/dotnet/sample/AutoGen.BasicSamples/Example13_OpenAIAgent_JsonMode.cs @@ -0,0 +1,68 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// Example13_OpenAIAgent_JsonMode.cs + +using System.Text.Json; +using System.Text.Json.Serialization; +using AutoGen.Core; +using AutoGen.OpenAI; +using AutoGen.OpenAI.Extension; +using Azure.AI.OpenAI; +using FluentAssertions; + +namespace AutoGen.BasicSample; + +public class Example13_OpenAIAgent_JsonMode +{ + public static async Task RunAsync() + { + #region create_agent + var config = LLMConfiguration.GetAzureOpenAIGPT3_5_Turbo(deployName: "gpt-35-turbo-0125"); // json mode only works with 0125 and later model. + var apiKey = config.ApiKey; + var endPoint = new Uri(config.Endpoint); + + var openAIClient = new OpenAIClient(endPoint, new Azure.AzureKeyCredential(apiKey)); + var openAIClientAgent = new OpenAIChatAgent( + openAIClient: openAIClient, + name: "assistant", + modelName: config.DeploymentName, + systemMessage: "You are a helpful assistant designed to output JSON.", + seed: 0, // explicitly set a seed to enable deterministic output + responseFormat: ChatCompletionsResponseFormat.JsonObject) // set response format to JSON object to enable JSON mode + .RegisterMessageConnector() + .RegisterPrintMessage(); + #endregion create_agent + + #region chat_with_agent + var reply = await openAIClientAgent.SendAsync("My name is John, I am 25 years old, and I live in Seattle."); + + var person = JsonSerializer.Deserialize(reply.GetContent()); + Console.WriteLine($"Name: {person.Name}"); + Console.WriteLine($"Age: {person.Age}"); + + if (!string.IsNullOrEmpty(person.Address)) + { + Console.WriteLine($"Address: {person.Address}"); + } + + Console.WriteLine("Done."); + #endregion chat_with_agent + + person.Name.Should().Be("John"); + person.Age.Should().Be(25); + person.Address.Should().BeNullOrEmpty(); + } +} + +#region person_class +public class Person +{ + [JsonPropertyName("name")] + public string Name { get; set; } + + [JsonPropertyName("age")] + public int Age { get; set; } + + [JsonPropertyName("address")] + public string Address { get; set; } +} +#endregion person_class diff --git a/dotnet/sample/AutoGen.BasicSamples/Example14_MistralClientAgent_TokenCount.cs b/dotnet/sample/AutoGen.BasicSamples/Example14_MistralClientAgent_TokenCount.cs new file mode 100644 index 00000000000..4c8794de961 --- /dev/null +++ b/dotnet/sample/AutoGen.BasicSamples/Example14_MistralClientAgent_TokenCount.cs @@ -0,0 +1,65 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// Example14_MistralClientAgent_TokenCount.cs + +#region using_statements +using AutoGen.Core; +using AutoGen.Mistral; +#endregion using_statements +using FluentAssertions; + +namespace AutoGen.BasicSample; + +public class Example14_MistralClientAgent_TokenCount +{ + #region token_counter_middleware + public class MistralAITokenCounterMiddleware : IMiddleware + { + private readonly List responses = new List(); + public string? Name => nameof(MistralAITokenCounterMiddleware); + + public async Task InvokeAsync(MiddlewareContext context, IAgent agent, CancellationToken cancellationToken = default) + { + var reply = await agent.GenerateReplyAsync(context.Messages, context.Options, cancellationToken); + + if (reply is IMessage message) + { + responses.Add(message.Content); + } + + return reply; + } + + public int GetCompletionTokenCount() + { + return responses.Sum(r => r.Usage.CompletionTokens); + } + } + #endregion token_counter_middleware + + public static async Task RunAsync() + { + #region create_mistral_client_agent + var apiKey = Environment.GetEnvironmentVariable("MISTRAL_API_KEY") ?? throw new Exception("Missing MISTRAL_API_KEY environment variable."); + var mistralClient = new MistralClient(apiKey); + var agent = new MistralClientAgent( + client: mistralClient, + name: "assistant", + model: MistralAIModelID.OPEN_MISTRAL_7B); + #endregion create_mistral_client_agent + + #region register_middleware + var tokenCounterMiddleware = new MistralAITokenCounterMiddleware(); + var mistralMessageConnector = new MistralChatMessageConnector(); + var agentWithTokenCounter = agent + .RegisterMiddleware(tokenCounterMiddleware) + .RegisterMiddleware(mistralMessageConnector) + .RegisterPrintMessage(); + #endregion register_middleware + + #region chat_with_agent + await agentWithTokenCounter.SendAsync("write a long, tedious story"); + Console.WriteLine($"Completion token count: {tokenCounterMiddleware.GetCompletionTokenCount()}"); + tokenCounterMiddleware.GetCompletionTokenCount().Should().BeGreaterThan(0); + #endregion chat_with_agent + } +} diff --git a/dotnet/sample/AutoGen.BasicSamples/Example15_GPT4V_BinaryDataImageMessage.cs b/dotnet/sample/AutoGen.BasicSamples/Example15_GPT4V_BinaryDataImageMessage.cs new file mode 100644 index 00000000000..f376342ed85 --- /dev/null +++ b/dotnet/sample/AutoGen.BasicSamples/Example15_GPT4V_BinaryDataImageMessage.cs @@ -0,0 +1,62 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// Example15_GPT4V_BinaryDataImageMessage.cs + +using AutoGen.Core; +using AutoGen.OpenAI; + +namespace AutoGen.BasicSample; + +/// +/// This example shows usage of ImageMessage. The image is loaded as BinaryData and sent to GPT-4V +///
+///
+/// Add additional images to the ImageResources to load and send more images to GPT-4V +///
+public static class Example15_GPT4V_BinaryDataImageMessage +{ + private static readonly string ImageResourcePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ImageResources"); + + private static Dictionary _mediaTypeMappings = new() + { + { ".png", "image/png" }, + { ".jpeg", "image/jpeg" }, + { ".jpg", "image/jpeg" }, + { ".gif", "image/gif" }, + { ".webp", "image/webp" } + }; + + public static async Task RunAsync() + { + var openAIKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new Exception("Please set OPENAI_API_KEY environment variable."); + var openAiConfig = new OpenAIConfig(openAIKey, "gpt-4-vision-preview"); + + var visionAgent = new GPTAgent( + name: "gpt", + systemMessage: "You are a helpful AI assistant", + config: openAiConfig, + temperature: 0); + + List messages = + [new TextMessage(Role.User, "What is this image?", from: "user")]; + AddMessagesFromResource(ImageResourcePath, messages); + + var multiModalMessage = new MultiModalMessage(Role.User, messages, from: "user"); + var response = await visionAgent.SendAsync(multiModalMessage); + } + + private static void AddMessagesFromResource(string imageResourcePath, List messages) + { + foreach (string file in Directory.GetFiles(imageResourcePath)) + { + if (!_mediaTypeMappings.TryGetValue(Path.GetExtension(file).ToLowerInvariant(), out var mediaType)) + continue; + + using var fs = new FileStream(file, FileMode.Open, FileAccess.Read); + var ms = new MemoryStream(); + fs.CopyTo(ms); + ms.Seek(0, SeekOrigin.Begin); + var imageData = BinaryData.FromStream(ms, mediaType); + messages.Add(new ImageMessage(Role.Assistant, imageData, from: "user")); + } + } +} diff --git a/dotnet/sample/AutoGen.BasicSamples/Example16_OpenAIChatAgent_ConnectToThirdPartyBackend.cs b/dotnet/sample/AutoGen.BasicSamples/Example16_OpenAIChatAgent_ConnectToThirdPartyBackend.cs new file mode 100644 index 00000000000..eb8bcb179be --- /dev/null +++ b/dotnet/sample/AutoGen.BasicSamples/Example16_OpenAIChatAgent_ConnectToThirdPartyBackend.cs @@ -0,0 +1,62 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// Example16_OpenAIChatAgent_ConnectToThirdPartyBackend.cs +#region using_statement +using AutoGen.Core; +using AutoGen.OpenAI; +using AutoGen.OpenAI.Extension; +using Azure.AI.OpenAI; +using Azure.Core.Pipeline; +#endregion using_statement + +namespace AutoGen.BasicSample; + +#region CustomHttpClientHandler +public sealed class CustomHttpClientHandler : HttpClientHandler +{ + private string _modelServiceUrl; + + public CustomHttpClientHandler(string modelServiceUrl) + { + _modelServiceUrl = modelServiceUrl; + } + + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + request.RequestUri = new Uri($"{_modelServiceUrl}{request.RequestUri.PathAndQuery}"); + + return base.SendAsync(request, cancellationToken); + } +} +#endregion CustomHttpClientHandler + +public class Example16_OpenAIChatAgent_ConnectToThirdPartyBackend +{ + public static async Task RunAsync() + { + #region create_agent + using var client = new HttpClient(new CustomHttpClientHandler("http://localhost:11434")); + var option = new OpenAIClientOptions(OpenAIClientOptions.ServiceVersion.V2024_04_01_Preview) + { + Transport = new HttpClientTransport(client), + }; + + // api-key is not required for local server + // so you can use any string here + var openAIClient = new OpenAIClient("api-key", option); + var model = "llama3"; + + var agent = new OpenAIChatAgent( + openAIClient: openAIClient, + name: "assistant", + modelName: model, + systemMessage: "You are a helpful assistant designed to output JSON.", + seed: 0) + .RegisterMessageConnector() + .RegisterPrintMessage(); + #endregion create_agent + + #region send_message + await agent.SendAsync("Can you write a piece of C# code to calculate 100th of fibonacci?"); + #endregion send_message + } +} diff --git a/dotnet/sample/AutoGen.BasicSamples/GlobalUsing.cs b/dotnet/sample/AutoGen.BasicSamples/GlobalUsing.cs new file mode 100644 index 00000000000..87b4ee0ab4c --- /dev/null +++ b/dotnet/sample/AutoGen.BasicSamples/GlobalUsing.cs @@ -0,0 +1,3 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// GlobalUsing.cs + diff --git a/dotnet/sample/AutoGen.BasicSamples/ImageResources/square.png b/dotnet/sample/AutoGen.BasicSamples/ImageResources/square.png new file mode 100644 index 00000000000..afb4f4cd4df --- /dev/null +++ b/dotnet/sample/AutoGen.BasicSamples/ImageResources/square.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8323d0b8eceb752e14c29543b2e28bb2fc648ed9719095c31b7708867a4dc918 +size 491 diff --git a/dotnet/sample/AutoGen.BasicSamples/LLMConfiguration.cs b/dotnet/sample/AutoGen.BasicSamples/LLMConfiguration.cs new file mode 100644 index 00000000000..37c9b0d7ade --- /dev/null +++ b/dotnet/sample/AutoGen.BasicSamples/LLMConfiguration.cs @@ -0,0 +1,40 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// LLMConfiguration.cs + +using AutoGen.OpenAI; + +namespace AutoGen.BasicSample; + +internal static class LLMConfiguration +{ + public static OpenAIConfig GetOpenAIGPT3_5_Turbo() + { + var openAIKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new Exception("Please set OPENAI_API_KEY environment variable."); + var modelId = "gpt-3.5-turbo"; + return new OpenAIConfig(openAIKey, modelId); + } + + public static OpenAIConfig GetOpenAIGPT4() + { + var openAIKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new Exception("Please set OPENAI_API_KEY environment variable."); + var modelId = "gpt-4"; + + return new OpenAIConfig(openAIKey, modelId); + } + + public static AzureOpenAIConfig GetAzureOpenAIGPT3_5_Turbo(string deployName = "gpt-35-turbo-16k") + { + var azureOpenAIKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY") ?? throw new Exception("Please set AZURE_OPENAI_API_KEY environment variable."); + var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new Exception("Please set AZURE_OPENAI_ENDPOINT environment variable."); + + return new AzureOpenAIConfig(endpoint, deployName, azureOpenAIKey); + } + + public static AzureOpenAIConfig GetAzureOpenAIGPT4(string deployName = "gpt-4") + { + var azureOpenAIKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY") ?? throw new Exception("Please set AZURE_OPENAI_API_KEY environment variable."); + var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new Exception("Please set AZURE_OPENAI_ENDPOINT environment variable."); + + return new AzureOpenAIConfig(endpoint, deployName, azureOpenAIKey); + } +} diff --git a/dotnet/sample/AutoGen.BasicSamples/Program.cs b/dotnet/sample/AutoGen.BasicSamples/Program.cs new file mode 100644 index 00000000000..11b5127ade0 --- /dev/null +++ b/dotnet/sample/AutoGen.BasicSamples/Program.cs @@ -0,0 +1,6 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// Program.cs + +using AutoGen.BasicSample; +Console.ReadLine(); +await Example16_OpenAIChatAgent_ConnectToThirdPartyBackend.RunAsync(); diff --git a/dotnet/src/AutoGen.Core/Agent/DefaultReplyAgent.cs b/dotnet/src/AutoGen.Core/Agent/DefaultReplyAgent.cs new file mode 100644 index 00000000000..647a2ece79d --- /dev/null +++ b/dotnet/src/AutoGen.Core/Agent/DefaultReplyAgent.cs @@ -0,0 +1,31 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// DefaultReplyAgent.cs + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace AutoGen.Core; + +public class DefaultReplyAgent : IAgent +{ + public DefaultReplyAgent( + string name, + string? defaultReply) + { + Name = name; + DefaultReply = defaultReply ?? string.Empty; + } + + public string Name { get; } + + public string DefaultReply { get; } = string.Empty; + + public async Task GenerateReplyAsync( + IEnumerable _, + GenerateReplyOptions? __ = null, + CancellationToken ___ = default) + { + return new TextMessage(Role.Assistant, DefaultReply, from: this.Name); + } +} diff --git a/dotnet/src/AutoGen.Core/Agent/GroupChatManager.cs b/dotnet/src/AutoGen.Core/Agent/GroupChatManager.cs new file mode 100644 index 00000000000..db40f801dea --- /dev/null +++ b/dotnet/src/AutoGen.Core/Agent/GroupChatManager.cs @@ -0,0 +1,34 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// GroupChatManager.cs + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace AutoGen.Core; + +public class GroupChatManager : IAgent +{ + public GroupChatManager(IGroupChat groupChat) + { + GroupChat = groupChat; + } + public string Name => throw new ArgumentException("GroupChatManager does not have a name"); + + public IEnumerable? Messages { get; private set; } + + public IGroupChat GroupChat { get; } + + public async Task GenerateReplyAsync( + IEnumerable messages, + GenerateReplyOptions? options, + CancellationToken cancellationToken = default) + { + var response = await GroupChat.CallAsync(messages, ct: cancellationToken); + Messages = response; + + return response.Last(); + } +} diff --git a/dotnet/src/AutoGen.Core/Agent/IAgent.cs b/dotnet/src/AutoGen.Core/Agent/IAgent.cs new file mode 100644 index 00000000000..b9149008480 --- /dev/null +++ b/dotnet/src/AutoGen.Core/Agent/IAgent.cs @@ -0,0 +1,50 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// IAgent.cs + +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace AutoGen.Core; +public interface IAgent +{ + public string Name { get; } + + /// + /// Generate reply + /// + /// conversation history + /// completion option. If provided, it should override existing option if there's any + public Task GenerateReplyAsync( + IEnumerable messages, + GenerateReplyOptions? options = null, + CancellationToken cancellationToken = default); +} + +public class GenerateReplyOptions +{ + public GenerateReplyOptions() + { + } + + /// + /// Copy constructor + /// + /// other option to copy from + public GenerateReplyOptions(GenerateReplyOptions other) + { + this.Temperature = other.Temperature; + this.MaxToken = other.MaxToken; + this.StopSequence = other.StopSequence?.Select(s => s)?.ToArray(); + this.Functions = other.Functions?.Select(f => f)?.ToArray(); + } + + public float? Temperature { get; set; } + + public int? MaxToken { get; set; } + + public string[]? StopSequence { get; set; } + + public FunctionContract[]? Functions { get; set; } +} diff --git a/dotnet/src/AutoGen.Core/Agent/IMiddlewareAgent.cs b/dotnet/src/AutoGen.Core/Agent/IMiddlewareAgent.cs new file mode 100644 index 00000000000..a0b01e7c3e2 --- /dev/null +++ b/dotnet/src/AutoGen.Core/Agent/IMiddlewareAgent.cs @@ -0,0 +1,54 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// IMiddlewareAgent.cs + +using System.Collections.Generic; + +namespace AutoGen.Core; + +public interface IMiddlewareAgent : IAgent +{ + /// + /// Get the inner agent. + /// + IAgent Agent { get; } + + /// + /// Get the middlewares. + /// + IEnumerable Middlewares { get; } + + /// + /// Use middleware. + /// + void Use(IMiddleware middleware); +} + +public interface IMiddlewareStreamAgent : IStreamingAgent +{ + /// + /// Get the inner agent. + /// + IStreamingAgent StreamingAgent { get; } + + IEnumerable StreamingMiddlewares { get; } + + void UseStreaming(IStreamingMiddleware middleware); +} + +public interface IMiddlewareAgent : IMiddlewareAgent + where T : IAgent +{ + /// + /// Get the typed inner agent. + /// + T TAgent { get; } +} + +public interface IMiddlewareStreamAgent : IMiddlewareStreamAgent + where T : IStreamingAgent +{ + /// + /// Get the typed inner agent. + /// + T TStreamingAgent { get; } +} diff --git a/dotnet/src/AutoGen.Core/Agent/IStreamingAgent.cs b/dotnet/src/AutoGen.Core/Agent/IStreamingAgent.cs new file mode 100644 index 00000000000..665f18bac12 --- /dev/null +++ b/dotnet/src/AutoGen.Core/Agent/IStreamingAgent.cs @@ -0,0 +1,18 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// IStreamingAgent.cs + +using System.Collections.Generic; +using System.Threading; + +namespace AutoGen.Core; + +/// +/// agent that supports streaming reply +/// +public interface IStreamingAgent : IAgent +{ + public IAsyncEnumerable GenerateStreamingReplyAsync( + IEnumerable messages, + GenerateReplyOptions? options = null, + CancellationToken cancellationToken = default); +} diff --git a/dotnet/src/AutoGen.Core/Agent/MiddlewareAgent.cs b/dotnet/src/AutoGen.Core/Agent/MiddlewareAgent.cs new file mode 100644 index 00000000000..84d0d4b59e6 --- /dev/null +++ b/dotnet/src/AutoGen.Core/Agent/MiddlewareAgent.cs @@ -0,0 +1,140 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// MiddlewareAgent.cs + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace AutoGen.Core; + +/// +/// An agent that allows you to add middleware and modify the behavior of an existing agent. +/// +public class MiddlewareAgent : IMiddlewareAgent +{ + private IAgent _agent; + private readonly List middlewares = new(); + + /// + /// Create a new instance of + /// + /// the inner agent where middleware will be added. + /// the name of the agent if provided. Otherwise, the name of will be used. + public MiddlewareAgent(IAgent innerAgent, string? name = null, IEnumerable? middlewares = null) + { + this.Name = name ?? innerAgent.Name; + this._agent = innerAgent; + if (middlewares != null && middlewares.Any()) + { + foreach (var middleware in middlewares) + { + this.Use(middleware); + } + } + } + + /// + /// Create a new instance of by copying the middlewares from another . + /// + public MiddlewareAgent(MiddlewareAgent other) + { + this.Name = other.Name; + this._agent = other._agent; + this.middlewares.AddRange(other.middlewares); + } + + public string Name { get; } + + /// + /// Get the inner agent. + /// + public IAgent Agent => this._agent; + + /// + /// Get the middlewares. + /// + public IEnumerable Middlewares => this.middlewares; + + public Task GenerateReplyAsync( + IEnumerable messages, + GenerateReplyOptions? options = null, + CancellationToken cancellationToken = default) + { + return _agent.GenerateReplyAsync(messages, options, cancellationToken); + } + + /// + /// Add a middleware to the agent. If multiple middlewares are added, they will be executed in the LIFO order. + /// Call into the next function to continue the execution of the next middleware. + /// Short cut middleware execution by not calling into the next function. + /// + public void Use(Func, GenerateReplyOptions?, IAgent, CancellationToken, Task> func, string? middlewareName = null) + { + var middleware = new DelegateMiddleware(middlewareName, async (context, agent, cancellationToken) => + { + return await func(context.Messages, context.Options, agent, cancellationToken); + }); + + this.Use(middleware); + } + + public void Use(IMiddleware middleware) + { + this.middlewares.Add(middleware); + _agent = new DelegateAgent(middleware, _agent); + } + + public override string ToString() + { + var names = this.Middlewares.Select(m => m.Name ?? "[Unknown middleware]"); + var namesPlusAgentName = names.Append(this.Name); + + return namesPlusAgentName.Aggregate((a, b) => $"{a} -> {b}"); + } + + private class DelegateAgent : IAgent + { + private readonly IAgent innerAgent; + private readonly IMiddleware middleware; + + public DelegateAgent(IMiddleware middleware, IAgent innerAgent) + { + this.middleware = middleware; + this.innerAgent = innerAgent; + } + + public string Name { get => this.innerAgent.Name; } + + public Task GenerateReplyAsync( + IEnumerable messages, + GenerateReplyOptions? options = null, + CancellationToken cancellationToken = default) + { + var context = new MiddlewareContext(messages, options); + return this.middleware.InvokeAsync(context, this.innerAgent, cancellationToken); + } + } +} + +public sealed class MiddlewareAgent : MiddlewareAgent, IMiddlewareAgent + where T : IAgent +{ + public MiddlewareAgent(T innerAgent, string? name = null) + : base(innerAgent, name) + { + this.TAgent = innerAgent; + } + + public MiddlewareAgent(MiddlewareAgent other) + : base(other) + { + this.TAgent = other.TAgent; + } + + /// + /// Get the inner agent of type . + /// + public T TAgent { get; } +} diff --git a/dotnet/src/AutoGen.Core/Agent/MiddlewareStreamingAgent.cs b/dotnet/src/AutoGen.Core/Agent/MiddlewareStreamingAgent.cs new file mode 100644 index 00000000000..251d3c110f9 --- /dev/null +++ b/dotnet/src/AutoGen.Core/Agent/MiddlewareStreamingAgent.cs @@ -0,0 +1,119 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// MiddlewareStreamingAgent.cs + +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace AutoGen.Core; + +public class MiddlewareStreamingAgent : IMiddlewareStreamAgent +{ + private IStreamingAgent _agent; + private readonly List _streamingMiddlewares = new(); + + public MiddlewareStreamingAgent( + IStreamingAgent agent, + string? name = null, + IEnumerable? streamingMiddlewares = null) + { + this.Name = name ?? agent.Name; + _agent = agent; + + if (streamingMiddlewares != null && streamingMiddlewares.Any()) + { + foreach (var middleware in streamingMiddlewares) + { + this.UseStreaming(middleware); + } + } + } + + /// + /// Get the inner agent. + /// + public IStreamingAgent StreamingAgent => _agent; + + /// + /// Get the streaming middlewares. + /// + public IEnumerable StreamingMiddlewares => _streamingMiddlewares; + + public string Name { get; } + + public Task GenerateReplyAsync(IEnumerable messages, GenerateReplyOptions? options = null, CancellationToken cancellationToken = default) + { + return _agent.GenerateReplyAsync(messages, options, cancellationToken); + } + + public IAsyncEnumerable GenerateStreamingReplyAsync(IEnumerable messages, GenerateReplyOptions? options = null, CancellationToken cancellationToken = default) + { + + return _agent.GenerateStreamingReplyAsync(messages, options, cancellationToken); + } + + public void UseStreaming(IStreamingMiddleware middleware) + { + _streamingMiddlewares.Add(middleware); + _agent = new DelegateStreamingAgent(middleware, _agent); + } + + private class DelegateStreamingAgent : IStreamingAgent + { + private IStreamingMiddleware? streamingMiddleware; + private IStreamingAgent innerAgent; + + public string Name => innerAgent.Name; + + public DelegateStreamingAgent(IStreamingMiddleware middleware, IStreamingAgent next) + { + this.streamingMiddleware = middleware; + this.innerAgent = next; + } + + + public Task GenerateReplyAsync(IEnumerable messages, GenerateReplyOptions? options = null, CancellationToken cancellationToken = default) + { + if (this.streamingMiddleware is null) + { + return innerAgent.GenerateReplyAsync(messages, options, cancellationToken); + } + + var context = new MiddlewareContext(messages, options); + return this.streamingMiddleware.InvokeAsync(context, (IAgent)innerAgent, cancellationToken); + } + + public IAsyncEnumerable GenerateStreamingReplyAsync(IEnumerable messages, GenerateReplyOptions? options = null, CancellationToken cancellationToken = default) + { + if (streamingMiddleware is null) + { + return innerAgent.GenerateStreamingReplyAsync(messages, options, cancellationToken); + } + + var context = new MiddlewareContext(messages, options); + return streamingMiddleware.InvokeAsync(context, innerAgent, cancellationToken); + } + } +} + +public sealed class MiddlewareStreamingAgent : MiddlewareStreamingAgent, IMiddlewareStreamAgent + where T : IStreamingAgent +{ + public MiddlewareStreamingAgent(T innerAgent, string? name = null, IEnumerable? streamingMiddlewares = null) + : base(innerAgent, name, streamingMiddlewares) + { + TStreamingAgent = innerAgent; + } + + public MiddlewareStreamingAgent(MiddlewareStreamingAgent other) + : base(other) + { + TStreamingAgent = other.TStreamingAgent; + } + + /// + /// Get the inner agent. + /// + public T TStreamingAgent { get; } +} diff --git a/dotnet/src/AutoGen.Core/AutoGen.Core.csproj b/dotnet/src/AutoGen.Core/AutoGen.Core.csproj new file mode 100644 index 00000000000..ebbec3f0a46 --- /dev/null +++ b/dotnet/src/AutoGen.Core/AutoGen.Core.csproj @@ -0,0 +1,22 @@ +ο»Ώ + + netstandard2.0 + AutoGen.Core + + + + + + + AutoGen.Core + + Core library for AutoGen. This package provides contracts and core functionalities for AutoGen. + + + + + + + + + diff --git a/dotnet/src/AutoGen.Core/Extension/AgentExtension.cs b/dotnet/src/AutoGen.Core/Extension/AgentExtension.cs new file mode 100644 index 00000000000..44ce8838b73 --- /dev/null +++ b/dotnet/src/AutoGen.Core/Extension/AgentExtension.cs @@ -0,0 +1,174 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// AgentExtension.cs + +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace AutoGen.Core; + +public static class AgentExtension +{ + /// + /// Send message to an agent. + /// + /// message to send. will be added to the end of if provided + /// sender agent. + /// chat history. + /// conversation history + public static async Task SendAsync( + this IAgent agent, + IMessage? message = null, + IEnumerable? chatHistory = null, + CancellationToken ct = default) + { + var messages = new List(); + + if (chatHistory != null) + { + messages.AddRange(chatHistory); + } + + if (message != null) + { + messages.Add(message); + } + + + var result = await agent.GenerateReplyAsync(messages, cancellationToken: ct); + + return result; + } + + /// + /// Send message to an agent. + /// + /// sender agent. + /// message to send. will be added to the end of if provided + /// chat history. + /// conversation history + public static async Task SendAsync( + this IAgent agent, + string message, + IEnumerable? chatHistory = null, + CancellationToken ct = default) + { + var msg = new TextMessage(Role.User, message); + + return await agent.SendAsync(msg, chatHistory, ct); + } + + /// + /// Send message to another agent. + /// + /// sender agent. + /// receiver agent. + /// chat history. + /// max conversation round. + /// conversation history + public static async Task> SendAsync( + this IAgent agent, + IAgent receiver, + IEnumerable chatHistory, + int maxRound = 10, + CancellationToken ct = default) + { + if (receiver is GroupChatManager manager) + { + var gc = manager.GroupChat; + + return await agent.SendMessageToGroupAsync(gc, chatHistory, maxRound, ct); + } + + var groupChat = new RoundRobinGroupChat( + agents: new[] + { + agent, + receiver, + }); + + return await groupChat.CallAsync(chatHistory, maxRound, ct: ct); + } + + /// + /// Send message to another agent. + /// + /// sender agent. + /// message to send. will be added to the end of if provided + /// receiver agent. + /// chat history. + /// max conversation round. + /// conversation history + public static async Task> SendAsync( + this IAgent agent, + IAgent receiver, + string message, + IEnumerable? chatHistory = null, + int maxRound = 10, + CancellationToken ct = default) + { + var msg = new TextMessage(Role.User, message) + { + From = agent.Name, + }; + + chatHistory = chatHistory ?? new List(); + chatHistory = chatHistory.Append(msg); + + return await agent.SendAsync(receiver, chatHistory, maxRound, ct); + } + + /// + /// Shortcut API to send message to another agent. + /// + /// sender agent + /// receiver agent + /// message to send + /// max round + public static async Task> InitiateChatAsync( + this IAgent agent, + IAgent receiver, + string? message = null, + int maxRound = 10, + CancellationToken ct = default) + { + var chatHistory = new List(); + if (message != null) + { + var msg = new TextMessage(Role.User, message) + { + From = agent.Name, + }; + + chatHistory.Add(msg); + } + + return await agent.SendAsync(receiver, chatHistory, maxRound, ct); + } + + public static async Task> SendMessageToGroupAsync( + this IAgent agent, + IGroupChat groupChat, + string msg, + IEnumerable? chatHistory = null, + int maxRound = 10, + CancellationToken ct = default) + { + var chatMessage = new TextMessage(Role.Assistant, msg, from: agent.Name); + chatHistory = chatHistory ?? Enumerable.Empty(); + chatHistory = chatHistory.Append(chatMessage); + + return await agent.SendMessageToGroupAsync(groupChat, chatHistory, maxRound, ct); + } + + public static async Task> SendMessageToGroupAsync( + this IAgent _, + IGroupChat groupChat, + IEnumerable? chatHistory = null, + int maxRound = 10, + CancellationToken ct = default) + { + return await groupChat.CallAsync(chatHistory, maxRound, ct); + } +} diff --git a/dotnet/src/AutoGen.Core/Extension/GroupChatExtension.cs b/dotnet/src/AutoGen.Core/Extension/GroupChatExtension.cs new file mode 100644 index 00000000000..e3e44622c81 --- /dev/null +++ b/dotnet/src/AutoGen.Core/Extension/GroupChatExtension.cs @@ -0,0 +1,109 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// GroupChatExtension.cs + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace AutoGen.Core; + +public static class GroupChatExtension +{ + public const string TERMINATE = "[GROUPCHAT_TERMINATE]"; + public const string CLEAR_MESSAGES = "[GROUPCHAT_CLEAR_MESSAGES]"; + + [Obsolete("please use SendIntroduction")] + public static void AddInitializeMessage(this IAgent agent, string message, IGroupChat groupChat) + { + var msg = new TextMessage(Role.User, message) + { + From = agent.Name + }; + + groupChat.SendIntroduction(msg); + } + + /// + /// Send an instruction message to the group chat. + /// + public static void SendIntroduction(this IAgent agent, string message, IGroupChat groupChat) + { + var msg = new TextMessage(Role.User, message) + { + From = agent.Name + }; + + groupChat.SendIntroduction(msg); + } + + public static IEnumerable MessageToKeep( + this IGroupChat _, + IEnumerable messages) + { + var lastCLRMessageIndex = messages.ToList() + .FindLastIndex(x => x.IsGroupChatClearMessage()); + + // if multiple clr messages, e.g [msg, clr, msg, clr, msg, clr, msg] + // only keep the the messages after the second last clr message. + if (messages.Count(m => m.IsGroupChatClearMessage()) > 1) + { + lastCLRMessageIndex = messages.ToList() + .FindLastIndex(lastCLRMessageIndex - 1, lastCLRMessageIndex - 1, x => x.IsGroupChatClearMessage()); + messages = messages.Skip(lastCLRMessageIndex); + } + + lastCLRMessageIndex = messages.ToList() + .FindLastIndex(x => x.IsGroupChatClearMessage()); + + if (lastCLRMessageIndex != -1 && messages.Count() - lastCLRMessageIndex >= 2) + { + messages = messages.Skip(lastCLRMessageIndex); + } + + return messages; + } + + /// + /// Return true if contains , otherwise false. + /// + /// + /// + public static bool IsGroupChatTerminateMessage(this IMessage message) + { + return message.GetContent()?.Contains(TERMINATE) ?? false; + } + + public static bool IsGroupChatClearMessage(this IMessage message) + { + return message.GetContent()?.Contains(CLEAR_MESSAGES) ?? false; + } + + public static IEnumerable ProcessConversationForAgent( + this IGroupChat groupChat, + IEnumerable initialMessages, + IEnumerable messages) + { + messages = groupChat.MessageToKeep(messages); + return initialMessages.Concat(messages); + } + + internal static IEnumerable ProcessConversationsForRolePlay( + this IGroupChat groupChat, + IEnumerable initialMessages, + IEnumerable messages) + { + messages = groupChat.MessageToKeep(messages); + var messagesToKeep = initialMessages.Concat(messages); + + return messagesToKeep.Select((x, i) => + { + var msg = @$"From {x.From}: +{x.GetContent()} + +round # + {i}"; + + return new TextMessage(Role.User, content: msg); + }); + } +} diff --git a/dotnet/src/AutoGen.Core/Extension/MessageExtension.cs b/dotnet/src/AutoGen.Core/Extension/MessageExtension.cs new file mode 100644 index 00000000000..47dbad55e30 --- /dev/null +++ b/dotnet/src/AutoGen.Core/Extension/MessageExtension.cs @@ -0,0 +1,213 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// MessageExtension.cs + +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace AutoGen.Core; + +public static class MessageExtension +{ + private static string separator = new string('-', 20); + + public static string FormatMessage(this IMessage message) + { + return message switch + { + Message msg => msg.FormatMessage(), + TextMessage textMessage => textMessage.FormatMessage(), + ImageMessage imageMessage => imageMessage.FormatMessage(), + ToolCallMessage toolCallMessage => toolCallMessage.FormatMessage(), + ToolCallResultMessage toolCallResultMessage => toolCallResultMessage.FormatMessage(), + AggregateMessage aggregateMessage => aggregateMessage.FormatMessage(), + _ => message.ToString(), + }; + } + + public static string FormatMessage(this TextMessage message) + { + var sb = new StringBuilder(); + // write from + sb.AppendLine($"TextMessage from {message.From}"); + // write a seperator + sb.AppendLine(separator); + sb.AppendLine(message.Content); + // write a seperator + sb.AppendLine(separator); + + return sb.ToString(); + } + + public static string FormatMessage(this ImageMessage message) + { + var sb = new StringBuilder(); + // write from + sb.AppendLine($"ImageMessage from {message.From}"); + // write a seperator + sb.AppendLine(separator); + sb.AppendLine($"Image: {message.Url}"); + // write a seperator + sb.AppendLine(separator); + + return sb.ToString(); + } + + public static string FormatMessage(this ToolCallMessage message) + { + var sb = new StringBuilder(); + // write from + sb.AppendLine($"ToolCallMessage from {message.From}"); + + // write a seperator + sb.AppendLine(separator); + + foreach (var toolCall in message.ToolCalls) + { + sb.AppendLine($"- {toolCall.FunctionName}: {toolCall.FunctionArguments}"); + } + + sb.AppendLine(separator); + + return sb.ToString(); + } + + public static string FormatMessage(this ToolCallResultMessage message) + { + var sb = new StringBuilder(); + // write from + sb.AppendLine($"ToolCallResultMessage from {message.From}"); + + // write a seperator + sb.AppendLine(separator); + + foreach (var toolCall in message.ToolCalls) + { + sb.AppendLine($"- {toolCall.FunctionName}: {toolCall.Result}"); + } + + sb.AppendLine(separator); + + return sb.ToString(); + } + + public static string FormatMessage(this AggregateMessage message) + { + var sb = new StringBuilder(); + // write from + sb.AppendLine($"AggregateMessage from {message.From}"); + + // write a seperator + sb.AppendLine(separator); + + sb.AppendLine("ToolCallMessage:"); + sb.AppendLine(message.Message1.FormatMessage()); + + sb.AppendLine("ToolCallResultMessage:"); + sb.AppendLine(message.Message2.FormatMessage()); + + sb.AppendLine(separator); + + return sb.ToString(); + } + public static string FormatMessage(this Message message) + { + var sb = new StringBuilder(); + // write from + sb.AppendLine($"Message from {message.From}"); + // write a seperator + sb.AppendLine(separator); + + // write content + sb.AppendLine($"content: {message.Content}"); + + // write function name if exists + if (!string.IsNullOrEmpty(message.FunctionName)) + { + sb.AppendLine($"function name: {message.FunctionName}"); + sb.AppendLine($"function arguments: {message.FunctionArguments}"); + } + + // write metadata + if (message.Metadata is { Count: > 0 }) + { + sb.AppendLine($"metadata:"); + foreach (var item in message.Metadata) + { + sb.AppendLine($"{item.Key}: {item.Value}"); + } + } + + // write a seperator + sb.AppendLine(separator); + + return sb.ToString(); + } + + public static bool IsSystemMessage(this IMessage message) + { + return message switch + { + TextMessage textMessage => textMessage.Role == Role.System, + Message msg => msg.Role == Role.System, + _ => false, + }; + } + + /// + /// Get the content from the message + /// if the message is a or , return the content + /// if the message is a and only contains one function call, return the result of that function call + /// if the message is a where TMessage1 is and TMessage2 is and the second message only contains one function call, return the result of that function call + /// for all other situation, return null. + /// + /// + public static string? GetContent(this IMessage message) + { + return message switch + { + TextMessage textMessage => textMessage.Content, + Message msg => msg.Content, + ToolCallResultMessage toolCallResultMessage => toolCallResultMessage.ToolCalls.Count == 1 ? toolCallResultMessage.ToolCalls.First().Result : null, + AggregateMessage aggregateMessage => aggregateMessage.Message2.ToolCalls.Count == 1 ? aggregateMessage.Message2.ToolCalls.First().Result : null, + _ => null, + }; + } + + /// + /// Get the role from the message if it's available. + /// + public static Role? GetRole(this IMessage message) + { + return message switch + { + TextMessage textMessage => textMessage.Role, + Message msg => msg.Role, + ImageMessage img => img.Role, + MultiModalMessage multiModal => multiModal.Role, + _ => null, + }; + } + + /// + /// Return the tool calls from the message if it's available. + /// if the message is a , return its tool calls + /// if the message is a and the function name and function arguments are available, return a list of tool call with one item + /// if the message is a where TMessage1 is and TMessage2 is , return the tool calls from the first message + /// + /// + /// + public static IList? GetToolCalls(this IMessage message) + { + return message switch + { + ToolCallMessage toolCallMessage => toolCallMessage.ToolCalls, + Message msg => msg.FunctionName is not null && msg.FunctionArguments is not null + ? msg.Content is not null ? new List { new ToolCall(msg.FunctionName, msg.FunctionArguments, result: msg.Content) } + : new List { new ToolCall(msg.FunctionName, msg.FunctionArguments) } + : null, + AggregateMessage aggregateMessage => aggregateMessage.Message1.ToolCalls, + _ => null, + }; + } +} diff --git a/dotnet/src/AutoGen.Core/Extension/MiddlewareExtension.cs b/dotnet/src/AutoGen.Core/Extension/MiddlewareExtension.cs new file mode 100644 index 00000000000..5beed7fd815 --- /dev/null +++ b/dotnet/src/AutoGen.Core/Extension/MiddlewareExtension.cs @@ -0,0 +1,145 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// MiddlewareExtension.cs + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace AutoGen.Core; + +public static class MiddlewareExtension +{ + /// + /// Register a auto reply hook to an agent. The hook will be called before the agent generate the reply. + /// If the hook return a non-null reply, then that non-null reply will be returned directly without calling the agent. + /// Otherwise, the agent will generate the reply. + /// This is useful when you want to override the agent reply in some cases. + /// + /// + /// + /// + /// throw when agent name is null. + [Obsolete("Use RegisterMiddleware instead.")] + public static MiddlewareAgent RegisterReply( + this TAgent agent, + Func, CancellationToken, Task> replyFunc) + where TAgent : IAgent + { + return agent.RegisterMiddleware(async (messages, options, agent, ct) => + { + var reply = await replyFunc(messages, ct); + + if (reply != null) + { + return reply; + } + + return await agent.GenerateReplyAsync(messages, options, ct); + }); + } + + /// + /// Register a post process hook to an agent. The hook will be called before the agent return the reply and after the agent generate the reply. + /// This is useful when you want to customize arbitrary behavior before the agent return the reply. + /// + /// One example is , which print the formatted message to console before the agent return the reply. + /// + /// throw when agent name is null. + [Obsolete("Use RegisterMiddleware instead.")] + public static MiddlewareAgent RegisterPostProcess( + this TAgent agent, + Func, IMessage, CancellationToken, Task> postprocessFunc) + where TAgent : IAgent + { + return agent.RegisterMiddleware(async (messages, options, agent, ct) => + { + var reply = await agent.GenerateReplyAsync(messages, options, ct); + + return await postprocessFunc(messages, reply, ct); + }); + } + + /// + /// Register a pre process hook to an agent. The hook will be called before the agent generate the reply. This is useful when you want to modify the conversation history before the agent generate the reply. + /// + /// throw when agent name is null. + [Obsolete("Use RegisterMiddleware instead.")] + public static MiddlewareAgent RegisterPreProcess( + this TAgent agent, + Func, CancellationToken, Task>> preprocessFunc) + where TAgent : IAgent + { + return agent.RegisterMiddleware(async (messages, options, agent, ct) => + { + var newMessages = await preprocessFunc(messages, ct); + + return await agent.GenerateReplyAsync(newMessages, options, ct); + }); + } + + /// + /// Register a middleware to an existing agent and return a new agent with the middleware. + /// To register a streaming middleware, use . + /// + public static MiddlewareAgent RegisterMiddleware( + this TAgent agent, + Func, GenerateReplyOptions?, IAgent, CancellationToken, Task> func, + string? middlewareName = null) + where TAgent : IAgent + { + var middleware = new DelegateMiddleware(middlewareName, async (context, agent, cancellationToken) => + { + return await func(context.Messages, context.Options, agent, cancellationToken); + }); + + return agent.RegisterMiddleware(middleware); + } + + /// + /// Register a middleware to an existing agent and return a new agent with the middleware. + /// To register a streaming middleware, use . + /// + public static MiddlewareAgent RegisterMiddleware( + this TAgent agent, + IMiddleware middleware) + where TAgent : IAgent + { + var middlewareAgent = new MiddlewareAgent(agent); + + return middlewareAgent.RegisterMiddleware(middleware); + } + + /// + /// Register a middleware to an existing agent and return a new agent with the middleware. + /// To register a streaming middleware, use . + /// + public static MiddlewareAgent RegisterMiddleware( + this MiddlewareAgent agent, + Func, GenerateReplyOptions?, IAgent, CancellationToken, Task> func, + string? middlewareName = null) + where TAgent : IAgent + { + var delegateMiddleware = new DelegateMiddleware(middlewareName, async (context, agent, cancellationToken) => + { + return await func(context.Messages, context.Options, agent, cancellationToken); + }); + + return agent.RegisterMiddleware(delegateMiddleware); + } + + /// + /// Register a middleware to an existing agent and return a new agent with the middleware. + /// To register a streaming middleware, use . + /// + public static MiddlewareAgent RegisterMiddleware( + this MiddlewareAgent agent, + IMiddleware middleware) + where TAgent : IAgent + { + var copyAgent = new MiddlewareAgent(agent); + copyAgent.Use(middleware); + + return copyAgent; + } +} diff --git a/dotnet/src/AutoGen.Core/Extension/PrintMessageMiddlewareExtension.cs b/dotnet/src/AutoGen.Core/Extension/PrintMessageMiddlewareExtension.cs new file mode 100644 index 00000000000..262b50d125d --- /dev/null +++ b/dotnet/src/AutoGen.Core/Extension/PrintMessageMiddlewareExtension.cs @@ -0,0 +1,69 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// PrintMessageMiddlewareExtension.cs + +using System; + +namespace AutoGen.Core; + +public static class PrintMessageMiddlewareExtension +{ + [Obsolete("This API will be removed in v0.1.0, Use RegisterPrintMessage instead.")] + public static MiddlewareAgent RegisterPrintFormatMessageHook(this TAgent agent) + where TAgent : IAgent + { + return RegisterPrintMessage(agent); + } + + [Obsolete("This API will be removed in v0.1.0, Use RegisterPrintMessage instead.")] + public static MiddlewareAgent RegisterPrintFormatMessageHook(this MiddlewareAgent agent) + where TAgent : IAgent + { + return RegisterPrintMessage(agent); + } + + [Obsolete("This API will be removed in v0.1.0, Use RegisterPrintMessage instead.")] + public static MiddlewareStreamingAgent RegisterPrintFormatMessageHook(this MiddlewareStreamingAgent agent) + where TAgent : IStreamingAgent + { + return RegisterPrintMessage(agent); + } + + /// + /// Register a to which print formatted message to console. + /// + public static MiddlewareAgent RegisterPrintMessage(this TAgent agent) + where TAgent : IAgent + { + var middleware = new PrintMessageMiddleware(); + var middlewareAgent = new MiddlewareAgent(agent); + middlewareAgent.Use(middleware); + + return middlewareAgent; + } + + /// + /// Register a to which print formatted message to console. + /// + public static MiddlewareAgent RegisterPrintMessage(this MiddlewareAgent agent) + where TAgent : IAgent + { + var middleware = new PrintMessageMiddleware(); + var middlewareAgent = new MiddlewareAgent(agent); + middlewareAgent.Use(middleware); + + return middlewareAgent; + } + + /// + /// Register a to which print formatted message to console. + /// + public static MiddlewareStreamingAgent RegisterPrintMessage(this MiddlewareStreamingAgent agent) + where TAgent : IStreamingAgent + { + var middleware = new PrintMessageMiddleware(); + var middlewareAgent = new MiddlewareStreamingAgent(agent); + middlewareAgent.UseStreaming(middleware); + + return middlewareAgent; + } +} diff --git a/dotnet/src/AutoGen.Core/Extension/StreamingMiddlewareExtension.cs b/dotnet/src/AutoGen.Core/Extension/StreamingMiddlewareExtension.cs new file mode 100644 index 00000000000..2ec7b3f9f3b --- /dev/null +++ b/dotnet/src/AutoGen.Core/Extension/StreamingMiddlewareExtension.cs @@ -0,0 +1,37 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// StreamingMiddlewareExtension.cs + +namespace AutoGen.Core; + +public static class StreamingMiddlewareExtension +{ + /// + /// Register an to an existing and return a new agent with the registered middleware. + /// For registering an , please refer to + /// + public static MiddlewareStreamingAgent RegisterStreamingMiddleware( + this TStreamingAgent agent, + IStreamingMiddleware middleware) + where TStreamingAgent : IStreamingAgent + { + var middlewareAgent = new MiddlewareStreamingAgent(agent); + middlewareAgent.UseStreaming(middleware); + + return middlewareAgent; + } + + /// + /// Register an to an existing and return a new agent with the registered middleware. + /// For registering an , please refer to + /// + public static MiddlewareStreamingAgent RegisterStreamingMiddleware( + this MiddlewareStreamingAgent agent, + IStreamingMiddleware middleware) + where TAgent : IStreamingAgent + { + var copyAgent = new MiddlewareStreamingAgent(agent); + copyAgent.UseStreaming(middleware); + + return copyAgent; + } +} diff --git a/dotnet/src/AutoGen.Core/Function/FunctionAttribute.cs b/dotnet/src/AutoGen.Core/Function/FunctionAttribute.cs new file mode 100644 index 00000000000..2c828c26d89 --- /dev/null +++ b/dotnet/src/AutoGen.Core/Function/FunctionAttribute.cs @@ -0,0 +1,93 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// FunctionAttribute.cs + +using System; +using System.Collections.Generic; + +namespace AutoGen.Core; + +[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)] +public class FunctionAttribute : Attribute +{ + public string? FunctionName { get; } + + public string? Description { get; } + + public FunctionAttribute(string? functionName = null, string? description = null) + { + FunctionName = functionName; + Description = description; + } +} + +public class FunctionContract +{ + /// + /// The namespace of the function. + /// + public string? Namespace { get; set; } + + /// + /// The class name of the function. + /// + public string? ClassName { get; set; } + + /// + /// The name of the function. + /// + public string? Name { get; set; } + + /// + /// The description of the function. + /// If a structured comment is available, the description will be extracted from the summary section. + /// Otherwise, the description will be null. + /// + public string? Description { get; set; } + + /// + /// The parameters of the function. + /// + public IEnumerable? Parameters { get; set; } + + /// + /// The return type of the function. + /// + public Type? ReturnType { get; set; } + + /// + /// The description of the return section. + /// If a structured comment is available, the description will be extracted from the return section. + /// Otherwise, the description will be null. + /// + public string? ReturnDescription { get; set; } +} + +public class FunctionParameterContract +{ + /// + /// The name of the parameter. + /// + public string? Name { get; set; } + + /// + /// The description of the parameter. + /// This will be extracted from the param section of the structured comment if available. + /// Otherwise, the description will be null. + /// + public string? Description { get; set; } + + /// + /// The type of the parameter. + /// + public Type? ParameterType { get; set; } + + /// + /// If the parameter is a required parameter. + /// + public bool IsRequired { get; set; } + + /// + /// The default value of the parameter. + /// + public object? DefaultValue { get; set; } +} diff --git a/dotnet/src/AutoGen.Core/GroupChat/Graph.cs b/dotnet/src/AutoGen.Core/GroupChat/Graph.cs new file mode 100644 index 00000000000..02f4da50bae --- /dev/null +++ b/dotnet/src/AutoGen.Core/GroupChat/Graph.cs @@ -0,0 +1,104 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// Graph.cs + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace AutoGen.Core; + +public class Graph +{ + private readonly List transitions = new List(); + + public Graph(IEnumerable transitions) + { + this.transitions.AddRange(transitions); + } + + public void AddTransition(Transition transition) + { + transitions.Add(transition); + } + + /// + /// Get the transitions of the workflow. + /// + public IEnumerable Transitions => transitions; + + /// + /// Get the next available agents that the messages can be transit to. + /// + /// the from agent + /// messages + /// A list of agents that the messages can be transit to + public async Task> TransitToNextAvailableAgentsAsync(IAgent fromAgent, IEnumerable messages) + { + var nextAgents = new List(); + var availableTransitions = transitions.FindAll(t => t.From == fromAgent) ?? Enumerable.Empty(); + foreach (var transition in availableTransitions) + { + if (await transition.CanTransitionAsync(messages)) + { + nextAgents.Add(transition.To); + } + } + + return nextAgents; + } +} + +/// +/// Represents a transition between two agents. +/// +public class Transition +{ + private readonly IAgent _from; + private readonly IAgent _to; + private readonly Func, Task>? _canTransition; + + /// + /// Create a new instance of . + /// This constructor is used for testing purpose only. + /// To create a new instance of , use . + /// + /// from agent + /// to agent + /// detect if the transition is allowed, default to be always true + internal Transition(IAgent from, IAgent to, Func, Task>? canTransitionAsync = null) + { + _from = from; + _to = to; + _canTransition = canTransitionAsync; + } + + /// + /// Create a new instance of . + /// + /// " + public static Transition Create(TFromAgent from, TToAgent to, Func, Task>? canTransitionAsync = null) + where TFromAgent : IAgent + where TToAgent : IAgent + { + return new Transition(from, to, (fromAgent, toAgent, messages) => canTransitionAsync?.Invoke((TFromAgent)fromAgent, (TToAgent)toAgent, messages) ?? Task.FromResult(true)); + } + + public IAgent From => _from; + + public IAgent To => _to; + + /// + /// Check if the transition is allowed. + /// + /// messages + public Task CanTransitionAsync(IEnumerable messages) + { + if (_canTransition == null) + { + return Task.FromResult(true); + } + + return _canTransition(this.From, this.To, messages); + } +} diff --git a/dotnet/src/AutoGen.Core/GroupChat/GroupChat.cs b/dotnet/src/AutoGen.Core/GroupChat/GroupChat.cs new file mode 100644 index 00000000000..3b6288ca0a7 --- /dev/null +++ b/dotnet/src/AutoGen.Core/GroupChat/GroupChat.cs @@ -0,0 +1,183 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// GroupChat.cs + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace AutoGen.Core; + +public class GroupChat : IGroupChat +{ + private IAgent? admin; + private List agents = new List(); + private IEnumerable initializeMessages = new List(); + private Graph? workflow = null; + + public IEnumerable? Messages { get; private set; } + + /// + /// Create a group chat. The next speaker will be decided by a combination effort of the admin and the workflow. + /// + /// admin agent. If provided, the admin will be invoked to decide the next speaker. + /// workflow of the group chat. If provided, the next speaker will be decided by the workflow. + /// group members. + /// + public GroupChat( + IEnumerable members, + IAgent? admin = null, + IEnumerable? initializeMessages = null, + Graph? workflow = null) + { + this.admin = admin; + this.agents = members.ToList(); + this.initializeMessages = initializeMessages ?? new List(); + this.workflow = workflow; + + this.Validation(); + } + + private void Validation() + { + // check if all agents has a name + if (this.agents.Any(x => string.IsNullOrEmpty(x.Name))) + { + throw new Exception("All agents must have a name."); + } + + // check if any agents has the same name + var names = this.agents.Select(x => x.Name).ToList(); + if (names.Distinct().Count() != names.Count) + { + throw new Exception("All agents must have a unique name."); + } + + // if there's a workflow + // check if the agents in that workflow are in the group chat + if (this.workflow != null) + { + var agentNamesInWorkflow = this.workflow.Transitions.Select(x => x.From.Name!).Concat(this.workflow.Transitions.Select(x => x.To.Name!)).Distinct(); + if (agentNamesInWorkflow.Any(x => !this.agents.Select(a => a.Name).Contains(x))) + { + throw new Exception("All agents in the workflow must be in the group chat."); + } + } + + // must provide one of admin or workflow + if (this.admin == null && this.workflow == null) + { + throw new Exception("Must provide one of admin or workflow."); + } + } + + /// + /// Select the next speaker based on the conversation history. + /// The next speaker will be decided by a combination effort of the admin and the workflow. + /// Firstly, a group of candidates will be selected by the workflow. If there's only one candidate, then that candidate will be the next speaker. + /// Otherwise, the admin will be invoked to decide the next speaker using role-play prompt. + /// + /// current speaker + /// conversation history + /// next speaker. + public async Task SelectNextSpeakerAsync(IAgent currentSpeaker, IEnumerable conversationHistory) + { + var agentNames = this.agents.Select(x => x.Name).ToList(); + if (this.workflow != null) + { + var nextAvailableAgents = await this.workflow.TransitToNextAvailableAgentsAsync(currentSpeaker, conversationHistory); + agentNames = nextAvailableAgents.Select(x => x.Name).ToList(); + if (agentNames.Count() == 0) + { + throw new Exception("No next available agents found in the current workflow"); + } + + if (agentNames.Count() == 1) + { + return this.agents.FirstOrDefault(x => x.Name == agentNames.First()); + } + } + + if (this.admin == null) + { + throw new Exception("No admin is provided."); + } + + var systemMessage = new TextMessage(Role.System, + content: $@"You are in a role play game. Carefully read the conversation history and carry on the conversation. +The available roles are: +{string.Join(",", agentNames)} + +Each message will start with 'From name:', e.g: +From admin: +//your message//."); + + var conv = this.ProcessConversationsForRolePlay(this.initializeMessages, conversationHistory); + + var messages = new IMessage[] { systemMessage }.Concat(conv); + var response = await this.admin.GenerateReplyAsync( + messages: messages, + options: new GenerateReplyOptions + { + Temperature = 0, + MaxToken = 128, + StopSequence = [":"], + Functions = [], + }); + + var name = response?.GetContent() ?? throw new Exception("No name is returned."); + + // remove From + name = name!.Substring(5); + return this.agents.First(x => x.Name!.ToLower() == name.ToLower()); + } + + /// + public void AddInitializeMessage(IMessage message) + { + this.SendIntroduction(message); + } + + public async Task> CallAsync( + IEnumerable? conversationWithName = null, + int maxRound = 10, + CancellationToken ct = default) + { + var conversationHistory = new List(); + if (conversationWithName != null) + { + conversationHistory.AddRange(conversationWithName); + } + + var lastSpeaker = conversationHistory.LastOrDefault()?.From switch + { + null => this.agents.First(), + _ => this.agents.FirstOrDefault(x => x.Name == conversationHistory.Last().From) ?? throw new Exception("The agent is not in the group chat"), + }; + var round = 0; + while (round < maxRound) + { + var currentSpeaker = await this.SelectNextSpeakerAsync(lastSpeaker, conversationHistory); + var processedConversation = this.ProcessConversationForAgent(this.initializeMessages, conversationHistory); + var result = await currentSpeaker.GenerateReplyAsync(processedConversation) ?? throw new Exception("No result is returned."); + conversationHistory.Add(result); + + // if message is terminate message, then terminate the conversation + if (result?.IsGroupChatTerminateMessage() ?? false) + { + break; + } + + lastSpeaker = currentSpeaker; + round++; + } + + return conversationHistory; + } + + public void SendIntroduction(IMessage message) + { + this.initializeMessages = this.initializeMessages.Append(message); + } +} diff --git a/dotnet/src/AutoGen.Core/GroupChat/RoundRobinGroupChat.cs b/dotnet/src/AutoGen.Core/GroupChat/RoundRobinGroupChat.cs new file mode 100644 index 00000000000..b8de89b834f --- /dev/null +++ b/dotnet/src/AutoGen.Core/GroupChat/RoundRobinGroupChat.cs @@ -0,0 +1,100 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// RoundRobinGroupChat.cs + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace AutoGen.Core; + +/// +/// Obsolete: please use +/// +[Obsolete("please use RoundRobinGroupChat")] +public class SequentialGroupChat : RoundRobinGroupChat +{ + [Obsolete("please use RoundRobinGroupChat")] + public SequentialGroupChat(IEnumerable agents, List? initializeMessages = null) + : base(agents, initializeMessages) + { + } +} + +/// +/// A group chat that allows agents to talk in a round-robin manner. +/// +public class RoundRobinGroupChat : IGroupChat +{ + private readonly List agents = new List(); + private readonly List initializeMessages = new List(); + + public RoundRobinGroupChat( + IEnumerable agents, + List? initializeMessages = null) + { + this.agents.AddRange(agents); + this.initializeMessages = initializeMessages ?? new List(); + } + + /// + public void AddInitializeMessage(IMessage message) + { + this.SendIntroduction(message); + } + + public async Task> CallAsync( + IEnumerable? conversationWithName = null, + int maxRound = 10, + CancellationToken ct = default) + { + var conversationHistory = new List(); + if (conversationWithName != null) + { + conversationHistory.AddRange(conversationWithName); + } + + var lastSpeaker = conversationHistory.LastOrDefault()?.From switch + { + null => this.agents.First(), + _ => this.agents.FirstOrDefault(x => x.Name == conversationHistory.Last().From) ?? throw new Exception("The agent is not in the group chat"), + }; + var round = 0; + while (round < maxRound) + { + var currentSpeaker = this.SelectNextSpeaker(lastSpeaker); + var processedConversation = this.ProcessConversationForAgent(this.initializeMessages, conversationHistory); + var result = await currentSpeaker.GenerateReplyAsync(processedConversation) ?? throw new Exception("No result is returned."); + conversationHistory.Add(result); + + // if message is terminate message, then terminate the conversation + if (result?.IsGroupChatTerminateMessage() ?? false) + { + break; + } + + lastSpeaker = currentSpeaker; + round++; + } + + return conversationHistory; + } + + public void SendIntroduction(IMessage message) + { + this.initializeMessages.Add(message); + } + + private IAgent SelectNextSpeaker(IAgent currentSpeaker) + { + var index = this.agents.IndexOf(currentSpeaker); + if (index == -1) + { + throw new ArgumentException("The agent is not in the group chat", nameof(currentSpeaker)); + } + + var nextIndex = (index + 1) % this.agents.Count; + return this.agents[nextIndex]; + } +} diff --git a/dotnet/src/AutoGen.Core/IGroupChat.cs b/dotnet/src/AutoGen.Core/IGroupChat.cs new file mode 100644 index 00000000000..a8c948cf58a --- /dev/null +++ b/dotnet/src/AutoGen.Core/IGroupChat.cs @@ -0,0 +1,22 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// IGroupChat.cs + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace AutoGen.Core; + +public interface IGroupChat +{ + /// + /// Send an introduction message to the group chat. + /// + void SendIntroduction(IMessage message); + + [Obsolete("please use SendIntroduction")] + void AddInitializeMessage(IMessage message); + + Task> CallAsync(IEnumerable? conversation = null, int maxRound = 10, CancellationToken ct = default); +} diff --git a/dotnet/src/AutoGen.Core/ILLMConfig.cs b/dotnet/src/AutoGen.Core/ILLMConfig.cs new file mode 100644 index 00000000000..fd2a90db02a --- /dev/null +++ b/dotnet/src/AutoGen.Core/ILLMConfig.cs @@ -0,0 +1,8 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// ILLMConfig.cs + +namespace AutoGen.Core; + +public interface ILLMConfig +{ +} diff --git a/dotnet/src/AutoGen.Core/Message/AggregateMessage.cs b/dotnet/src/AutoGen.Core/Message/AggregateMessage.cs new file mode 100644 index 00000000000..c7eee1316ee --- /dev/null +++ b/dotnet/src/AutoGen.Core/Message/AggregateMessage.cs @@ -0,0 +1,53 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// AggregateMessage.cs + +using System; +using System.Collections.Generic; + +namespace AutoGen.Core; + +public class AggregateMessage : IMessage + where TMessage1 : IMessage + where TMessage2 : IMessage +{ + public AggregateMessage(TMessage1 message1, TMessage2 message2, string? from = null) + { + this.From = from; + this.Message1 = message1; + this.Message2 = message2; + this.Validate(); + } + + public TMessage1 Message1 { get; } + + public TMessage2 Message2 { get; } + + public string? From { get; set; } + + private void Validate() + { + var messages = new List { this.Message1, this.Message2 }; + // the from property of all messages should be the same with the from property of the aggregate message + + foreach (var message in messages) + { + if (message.From != this.From) + { + throw new ArgumentException($"The from property of the message {message} is different from the from property of the aggregate message {this}"); + } + } + } + + public override string ToString() + { + var stringBuilder = new System.Text.StringBuilder(); + var messages = new List { this.Message1, this.Message2 }; + stringBuilder.Append($"AggregateMessage({this.From})"); + foreach (var message in messages) + { + stringBuilder.Append($"\n\t{message}"); + } + + return stringBuilder.ToString(); + } +} diff --git a/dotnet/src/AutoGen.Core/Message/IMessage.cs b/dotnet/src/AutoGen.Core/Message/IMessage.cs new file mode 100644 index 00000000000..7b48f4f0d63 --- /dev/null +++ b/dotnet/src/AutoGen.Core/Message/IMessage.cs @@ -0,0 +1,52 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// IMessage.cs + +namespace AutoGen.Core; + +/// +/// The universal message interface for all message types in AutoGen. +/// Related PR: https://github.com/microsoft/autogen/pull/1676 +/// Built-in message types +/// +/// +/// : plain text message. +/// +/// +/// : image message. +/// +/// +/// : message type for multimodal message. The current support message items are and . +/// +/// +/// : message type for tool call. This message supports both single and parallel tool call. +/// +/// +/// : message type for tool call result. +/// +/// +/// : This type is used by previous version of AutoGen. And it's reserved for backward compatibility. +/// +/// +/// : an aggregate message type that contains two message types. +/// This type is useful when you want to combine two message types into one unique message type. One example is when invoking a tool call and you want to return both and . +/// One example of how this type is used in AutoGen is +/// +/// +/// +public interface IMessage : IStreamingMessage +{ +} + +public interface IMessage : IMessage, IStreamingMessage +{ +} + +public interface IStreamingMessage +{ + string? From { get; set; } +} + +public interface IStreamingMessage : IStreamingMessage +{ + T Content { get; } +} diff --git a/dotnet/src/AutoGen.Core/Message/ImageMessage.cs b/dotnet/src/AutoGen.Core/Message/ImageMessage.cs new file mode 100644 index 00000000000..1239785c411 --- /dev/null +++ b/dotnet/src/AutoGen.Core/Message/ImageMessage.cs @@ -0,0 +1,61 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// ImageMessage.cs + +using System; + +namespace AutoGen.Core; + +public class ImageMessage : IMessage +{ + public ImageMessage(Role role, string url, string? from = null) + { + this.Role = role; + this.From = from; + this.Url = url; + } + + public ImageMessage(Role role, Uri uri, string? from = null) + { + this.Role = role; + this.From = from; + this.Url = uri.ToString(); + } + + public ImageMessage(Role role, BinaryData data, string? from = null) + { + if (data.IsEmpty) + { + throw new ArgumentException("Data cannot be empty", nameof(data)); + } + + if (string.IsNullOrWhiteSpace(data.MediaType)) + { + throw new ArgumentException("MediaType is needed for DataUri Images", nameof(data)); + } + + this.Role = role; + this.From = from; + this.Data = data; + } + + public Role Role { get; set; } + + public string? Url { get; set; } + + public string? From { get; set; } + + public BinaryData? Data { get; set; } + + public string BuildDataUri() + { + if (this.Data is null) + throw new NullReferenceException($"{nameof(Data)}"); + + return $"data:{this.Data.MediaType};base64,{Convert.ToBase64String(this.Data.ToArray())}"; + } + + public override string ToString() + { + return $"ImageMessage({this.Role}, {(this.Data != null ? BuildDataUri() : this.Url) ?? string.Empty}, {this.From})"; + } +} diff --git a/dotnet/src/AutoGen.Core/Message/Message.cs b/dotnet/src/AutoGen.Core/Message/Message.cs new file mode 100644 index 00000000000..ec4751b9344 --- /dev/null +++ b/dotnet/src/AutoGen.Core/Message/Message.cs @@ -0,0 +1,53 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// Message.cs + +using System.Collections.Generic; + +namespace AutoGen.Core; + +public class Message : IMessage +{ + public Message( + Role role, + string? content, + string? from = null, + ToolCall? toolCall = null) + { + this.Role = role; + this.Content = content; + this.From = from; + this.FunctionName = toolCall?.FunctionName; + this.FunctionArguments = toolCall?.FunctionArguments; + } + + public Message(Message other) + : this(other.Role, other.Content, other.From) + { + this.FunctionName = other.FunctionName; + this.FunctionArguments = other.FunctionArguments; + this.Value = other.Value; + this.Metadata = other.Metadata; + } + + public Role Role { get; set; } + + public string? Content { get; set; } + + public string? From { get; set; } + + public string? FunctionName { get; set; } + + public string? FunctionArguments { get; set; } + + /// + /// raw message + /// + public object? Value { get; set; } + + public IList> Metadata { get; set; } = new List>(); + + public override string ToString() + { + return $"Message({this.Role}, {this.Content}, {this.From}, {this.FunctionName}, {this.FunctionArguments})"; + } +} diff --git a/dotnet/src/AutoGen.Core/Message/MessageEnvelope.cs b/dotnet/src/AutoGen.Core/Message/MessageEnvelope.cs new file mode 100644 index 00000000000..f83bea27926 --- /dev/null +++ b/dotnet/src/AutoGen.Core/Message/MessageEnvelope.cs @@ -0,0 +1,37 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// MessageEnvelope.cs + +using System.Collections.Generic; + +namespace AutoGen.Core; + +public abstract class MessageEnvelope : IMessage, IStreamingMessage +{ + public MessageEnvelope(string? from = null, IDictionary? metadata = null) + { + this.From = from; + this.Metadata = metadata ?? new Dictionary(); + } + + public static MessageEnvelope Create(TContent content, string? from = null, IDictionary? metadata = null) + { + return new MessageEnvelope(content, from, metadata); + } + + public string? From { get; set; } + + public IDictionary Metadata { get; set; } +} + +public class MessageEnvelope : MessageEnvelope, IMessage, IStreamingMessage +{ + public MessageEnvelope(T content, string? from = null, IDictionary? metadata = null) + : base(from, metadata) + { + this.Content = content; + this.From = from; + this.Metadata = metadata ?? new Dictionary(); + } + + public T Content { get; } +} diff --git a/dotnet/src/AutoGen.Core/Message/MultiModalMessage.cs b/dotnet/src/AutoGen.Core/Message/MultiModalMessage.cs new file mode 100644 index 00000000000..9dd2a37af0b --- /dev/null +++ b/dotnet/src/AutoGen.Core/Message/MultiModalMessage.cs @@ -0,0 +1,58 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// MultiModalMessage.cs + +using System; +using System.Collections.Generic; + +namespace AutoGen.Core; + +public class MultiModalMessage : IMessage +{ + public MultiModalMessage(Role role, IEnumerable content, string? from = null) + { + this.Role = role; + this.Content = content; + this.From = from; + this.Validate(); + } + + public Role Role { get; set; } + + public IEnumerable Content { get; set; } + + public string? From { get; set; } + + private void Validate() + { + foreach (var message in this.Content) + { + if (message.From != this.From) + { + var reason = $"The from property of the message {message} is different from the from property of the aggregate message {this}"; + throw new ArgumentException($"Invalid aggregate message {reason}"); + } + } + + // all message must be either text or image + foreach (var message in this.Content) + { + if (message is not TextMessage && message is not ImageMessage) + { + var reason = $"The message {message} is not a text or image message"; + throw new ArgumentException($"Invalid aggregate message {reason}"); + } + } + } + + public override string ToString() + { + var stringBuilder = new System.Text.StringBuilder(); + stringBuilder.Append($"MultiModalMessage({this.Role}, {this.From})"); + foreach (var message in this.Content) + { + stringBuilder.Append($"\n\t{message}"); + } + + return stringBuilder.ToString(); + } +} diff --git a/dotnet/src/AutoGen.Core/Message/Role.cs b/dotnet/src/AutoGen.Core/Message/Role.cs new file mode 100644 index 00000000000..8253543a81c --- /dev/null +++ b/dotnet/src/AutoGen.Core/Message/Role.cs @@ -0,0 +1,54 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// Role.cs + +using System; + +namespace AutoGen.Core; + +public readonly struct Role : IEquatable +{ + private readonly string label; + + internal Role(string name) + { + label = name; + } + + public static Role User { get; } = new Role("user"); + + public static Role Assistant { get; } = new Role("assistant"); + + public static Role System { get; } = new Role("system"); + + public static Role Function { get; } = new Role("function"); + + public bool Equals(Role other) + { + return label.Equals(other.label, StringComparison.OrdinalIgnoreCase); + } + + public override string ToString() + { + return label; + } + + public override bool Equals(object? obj) + { + return obj is Role other && Equals(other); + } + + public override int GetHashCode() + { + return label.GetHashCode(); + } + + public static bool operator ==(Role left, Role right) + { + return left.Equals(right); + } + + public static bool operator !=(Role left, Role right) + { + return !(left == right); + } +} diff --git a/dotnet/src/AutoGen.Core/Message/TextMessage.cs b/dotnet/src/AutoGen.Core/Message/TextMessage.cs new file mode 100644 index 00000000000..ed4d7436dde --- /dev/null +++ b/dotnet/src/AutoGen.Core/Message/TextMessage.cs @@ -0,0 +1,63 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// TextMessage.cs + +namespace AutoGen.Core; + +public class TextMessage : IMessage, IStreamingMessage +{ + public TextMessage(Role role, string content, string? from = null) + { + this.Content = content; + this.Role = role; + this.From = from; + } + + public TextMessage(TextMessageUpdate update) + { + this.Content = update.Content ?? string.Empty; + this.Role = update.Role; + this.From = update.From; + } + + public void Update(TextMessageUpdate update) + { + if (update.Role != this.Role) + { + throw new System.ArgumentException("Role mismatch", nameof(update)); + } + + if (update.From != this.From) + { + throw new System.ArgumentException("From mismatch", nameof(update)); + } + + this.Content = this.Content + update.Content ?? string.Empty; + } + + public Role Role { get; set; } + + public string Content { get; set; } + + public string? From { get; set; } + + public override string ToString() + { + return $"TextMessage({this.Role}, {this.Content}, {this.From})"; + } +} + +public class TextMessageUpdate : IStreamingMessage +{ + public TextMessageUpdate(Role role, string? content, string? from = null) + { + this.Content = content; + this.From = from; + this.Role = role; + } + + public string? Content { get; set; } + + public string? From { get; set; } + + public Role Role { get; set; } +} diff --git a/dotnet/src/AutoGen.Core/Message/ToolCallMessage.cs b/dotnet/src/AutoGen.Core/Message/ToolCallMessage.cs new file mode 100644 index 00000000000..8dcd98ea0ec --- /dev/null +++ b/dotnet/src/AutoGen.Core/Message/ToolCallMessage.cs @@ -0,0 +1,108 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// ToolCallMessage.cs + +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace AutoGen.Core; + +public class ToolCall +{ + public ToolCall(string functionName, string functionArgs) + { + this.FunctionName = functionName; + this.FunctionArguments = functionArgs; + } + + public ToolCall(string functionName, string functionArgs, string result) + { + this.FunctionName = functionName; + this.FunctionArguments = functionArgs; + this.Result = result; + } + + public string FunctionName { get; set; } + + public string FunctionArguments { get; set; } + + public string? Result { get; set; } + + public override string ToString() + { + return $"ToolCall({this.FunctionName}, {this.FunctionArguments}, {this.Result})"; + } +} + +public class ToolCallMessage : IMessage +{ + public ToolCallMessage(IEnumerable toolCalls, string? from = null) + { + this.From = from; + this.ToolCalls = toolCalls.ToList(); + } + + public ToolCallMessage(string functionName, string functionArgs, string? from = null) + { + this.From = from; + this.ToolCalls = new List { new ToolCall(functionName, functionArgs) }; + } + + public ToolCallMessage(ToolCallMessageUpdate update) + { + this.From = update.From; + this.ToolCalls = new List { new ToolCall(update.FunctionName, update.FunctionArgumentUpdate) }; + } + + public void Update(ToolCallMessageUpdate update) + { + // firstly, valid if the update is from the same agent + if (update.From != this.From) + { + throw new System.ArgumentException("From mismatch", nameof(update)); + } + + // if update.FunctionName exists in the tool calls, update the function arguments + var toolCall = this.ToolCalls.FirstOrDefault(tc => tc.FunctionName == update.FunctionName); + if (toolCall is not null) + { + toolCall.FunctionArguments += update.FunctionArgumentUpdate; + } + else + { + this.ToolCalls.Add(new ToolCall(update.FunctionName, update.FunctionArgumentUpdate)); + } + } + + public IList ToolCalls { get; set; } + + public string? From { get; set; } + + public override string ToString() + { + var sb = new StringBuilder(); + sb.Append($"ToolCallMessage({this.From})"); + foreach (var toolCall in this.ToolCalls) + { + sb.Append($"\n\t{toolCall}"); + } + + return sb.ToString(); + } +} + +public class ToolCallMessageUpdate : IStreamingMessage +{ + public ToolCallMessageUpdate(string functionName, string functionArgumentUpdate, string? from = null) + { + this.From = from; + this.FunctionName = functionName; + this.FunctionArgumentUpdate = functionArgumentUpdate; + } + + public string? From { get; set; } + + public string FunctionName { get; set; } + + public string FunctionArgumentUpdate { get; set; } +} diff --git a/dotnet/src/AutoGen.Core/Message/ToolCallResultMessage.cs b/dotnet/src/AutoGen.Core/Message/ToolCallResultMessage.cs new file mode 100644 index 00000000000..99c7740849a --- /dev/null +++ b/dotnet/src/AutoGen.Core/Message/ToolCallResultMessage.cs @@ -0,0 +1,56 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// ToolCallResultMessage.cs + +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace AutoGen.Core; + +public class ToolCallResultMessage : IMessage +{ + public ToolCallResultMessage(IEnumerable toolCalls, string? from = null) + { + this.From = from; + this.ToolCalls = toolCalls.ToList(); + } + + public ToolCallResultMessage(string result, string functionName, string functionArgs, string? from = null) + { + this.From = from; + var toolCall = new ToolCall(functionName, functionArgs); + toolCall.Result = result; + this.ToolCalls = [toolCall]; + } + + /// + /// The original tool call message + /// + public IList ToolCalls { get; set; } + + public string? From { get; set; } + + public override string ToString() + { + var sb = new StringBuilder(); + sb.Append($"ToolCallResultMessage({this.From})"); + foreach (var toolCall in this.ToolCalls) + { + sb.Append($"\n\t{toolCall}"); + } + + return sb.ToString(); + } + + private void Validate() + { + // each tool call must have a result + foreach (var toolCall in this.ToolCalls) + { + if (string.IsNullOrEmpty(toolCall.Result)) + { + throw new System.ArgumentException($"The tool call {toolCall} does not have a result"); + } + } + } +} diff --git a/dotnet/src/AutoGen.Core/Middleware/DelegateMiddleware.cs b/dotnet/src/AutoGen.Core/Middleware/DelegateMiddleware.cs new file mode 100644 index 00000000000..79360e0428f --- /dev/null +++ b/dotnet/src/AutoGen.Core/Middleware/DelegateMiddleware.cs @@ -0,0 +1,45 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// DelegateMiddleware.cs + +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace AutoGen.Core; + +internal class DelegateMiddleware : IMiddleware +{ + /// + /// middleware delegate. Call into the next function to continue the execution of the next middleware. Otherwise, short cut the middleware execution. + /// + /// cancellation token + public delegate Task MiddlewareDelegate( + MiddlewareContext context, + IAgent agent, + CancellationToken cancellationToken); + + private readonly MiddlewareDelegate middlewareDelegate; + + public DelegateMiddleware(string? name, Func> middlewareDelegate) + { + this.Name = name; + this.middlewareDelegate = async (context, agent, cancellationToken) => + { + return await middlewareDelegate(context, agent, cancellationToken); + }; + } + + public string? Name { get; } + + public Task InvokeAsync( + MiddlewareContext context, + IAgent agent, + CancellationToken cancellationToken = default) + { + var messages = context.Messages; + var options = context.Options; + + return this.middlewareDelegate(context, agent, cancellationToken); + } +} + diff --git a/dotnet/src/AutoGen.Core/Middleware/FunctionCallMiddleware.cs b/dotnet/src/AutoGen.Core/Middleware/FunctionCallMiddleware.cs new file mode 100644 index 00000000000..2bc02805538 --- /dev/null +++ b/dotnet/src/AutoGen.Core/Middleware/FunctionCallMiddleware.cs @@ -0,0 +1,173 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// FunctionCallMiddleware.cs + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +namespace AutoGen.Core; + +/// +/// The middleware that process function call message that both send to an agent or reply from an agent. +/// If the last message is and the tool calls is available in this middleware's function map, +/// the tools from the last message will be invoked and a will be returned. In this situation, +/// the inner agent will be short-cut and won't be invoked. +/// Otherwise, the message will be sent to the inner agent. In this situation +/// if the reply from the inner agent is , +/// and the tool calls is available in this middleware's function map, the tools from the reply will be invoked, +/// and a where TMessage1 is and TMessage2 is "/> +/// will be returned. +/// +/// If the reply from the inner agent is but the tool calls is not available in this middleware's function map, +/// or the reply from the inner agent is not , the original reply from the inner agent will be returned. +/// +/// When used as a streaming middleware, if the streaming reply from the inner agent is or , +/// This middleware will update the message accordingly and invoke the function if the tool call is available in this middleware's function map. +/// If the streaming reply from the inner agent is other types of message, the most recent message will be used to invoke the function. +/// +/// +public class FunctionCallMiddleware : IStreamingMiddleware +{ + private readonly IEnumerable? functions; + private readonly IDictionary>>? functionMap; + + public FunctionCallMiddleware( + IEnumerable? functions = null, + IDictionary>>? functionMap = null, + string? name = null) + { + this.Name = name ?? nameof(FunctionCallMiddleware); + this.functions = functions; + this.functionMap = functionMap; + } + + public string? Name { get; } + + public async Task InvokeAsync(MiddlewareContext context, IAgent agent, CancellationToken cancellationToken = default) + { + var lastMessage = context.Messages.Last(); + if (lastMessage is ToolCallMessage toolCallMessage) + { + return await this.InvokeToolCallMessagesBeforeInvokingAgentAsync(toolCallMessage, agent); + } + + // combine functions + var options = new GenerateReplyOptions(context.Options ?? new GenerateReplyOptions()); + var combinedFunctions = this.functions?.Concat(options.Functions ?? []) ?? options.Functions; + options.Functions = combinedFunctions?.ToArray(); + + var reply = await agent.GenerateReplyAsync(context.Messages, options, cancellationToken); + + // if the reply is a function call message plus the function's name is available in function map, invoke the function and return the result instead of sending to the agent. + if (reply is ToolCallMessage toolCallMsg) + { + return await this.InvokeToolCallMessagesAfterInvokingAgentAsync(toolCallMsg, agent); + } + + // for all other messages, just return the reply from the agent. + return reply; + } + + public async IAsyncEnumerable InvokeAsync( + MiddlewareContext context, + IStreamingAgent agent, + [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + var lastMessage = context.Messages.Last(); + if (lastMessage is ToolCallMessage toolCallMessage) + { + yield return await this.InvokeToolCallMessagesBeforeInvokingAgentAsync(toolCallMessage, agent); + } + + // combine functions + var options = new GenerateReplyOptions(context.Options ?? new GenerateReplyOptions()); + var combinedFunctions = this.functions?.Concat(options.Functions ?? []) ?? options.Functions; + options.Functions = combinedFunctions?.ToArray(); + + IStreamingMessage? initMessage = default; + await foreach (var message in agent.GenerateStreamingReplyAsync(context.Messages, options, cancellationToken)) + { + if (message is ToolCallMessageUpdate toolCallMessageUpdate && this.functionMap != null) + { + if (initMessage is null) + { + initMessage = new ToolCallMessage(toolCallMessageUpdate); + } + else if (initMessage is ToolCallMessage toolCall) + { + toolCall.Update(toolCallMessageUpdate); + } + else + { + throw new InvalidOperationException("The first message is ToolCallMessage, but the update message is not ToolCallMessageUpdate"); + } + } + else + { + yield return message; + } + } + + if (initMessage is ToolCallMessage toolCallMsg) + { + yield return await this.InvokeToolCallMessagesAfterInvokingAgentAsync(toolCallMsg, agent); + } + } + + private async Task InvokeToolCallMessagesBeforeInvokingAgentAsync(ToolCallMessage toolCallMessage, IAgent agent) + { + var toolCallResult = new List(); + var toolCalls = toolCallMessage.ToolCalls; + foreach (var toolCall in toolCalls) + { + var functionName = toolCall.FunctionName; + var functionArguments = toolCall.FunctionArguments; + if (this.functionMap?.TryGetValue(functionName, out var func) is true) + { + var result = await func(functionArguments); + toolCallResult.Add(new ToolCall(functionName, functionArguments, result)); + } + else if (this.functionMap is not null) + { + var errorMessage = $"Function {functionName} is not available. Available functions are: {string.Join(", ", this.functionMap.Select(f => f.Key))}"; + + toolCallResult.Add(new ToolCall(functionName, functionArguments, errorMessage)); + } + else + { + throw new InvalidOperationException("FunctionMap is not available"); + } + } + + return new ToolCallResultMessage(toolCallResult, from: agent.Name); + } + + private async Task InvokeToolCallMessagesAfterInvokingAgentAsync(ToolCallMessage toolCallMsg, IAgent agent) + { + var toolCallsReply = toolCallMsg.ToolCalls; + var toolCallResult = new List(); + foreach (var toolCall in toolCallsReply) + { + var fName = toolCall.FunctionName; + var fArgs = toolCall.FunctionArguments; + if (this.functionMap?.TryGetValue(fName, out var func) is true) + { + var result = await func(fArgs); + toolCallResult.Add(new ToolCall(fName, fArgs, result)); + } + } + + if (toolCallResult.Count() > 0) + { + var toolCallResultMessage = new ToolCallResultMessage(toolCallResult, from: agent.Name); + return new AggregateMessage(toolCallMsg, toolCallResultMessage, from: agent.Name); + } + else + { + return toolCallMsg; + } + } +} diff --git a/dotnet/src/AutoGen.Core/Middleware/IMiddleware.cs b/dotnet/src/AutoGen.Core/Middleware/IMiddleware.cs new file mode 100644 index 00000000000..00ec5a97fc2 --- /dev/null +++ b/dotnet/src/AutoGen.Core/Middleware/IMiddleware.cs @@ -0,0 +1,26 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// IMiddleware.cs + +using System.Threading; +using System.Threading.Tasks; + +namespace AutoGen.Core; + +/// +/// The middleware interface. For streaming-version middleware, check . +/// +public interface IMiddleware +{ + /// + /// the name of the middleware + /// + public string? Name { get; } + + /// + /// The method to invoke the middleware + /// + public Task InvokeAsync( + MiddlewareContext context, + IAgent agent, + CancellationToken cancellationToken = default); +} diff --git a/dotnet/src/AutoGen.Core/Middleware/IStreamingMiddleware.cs b/dotnet/src/AutoGen.Core/Middleware/IStreamingMiddleware.cs new file mode 100644 index 00000000000..bc7aec57f52 --- /dev/null +++ b/dotnet/src/AutoGen.Core/Middleware/IStreamingMiddleware.cs @@ -0,0 +1,21 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// IStreamingMiddleware.cs + +using System.Collections.Generic; +using System.Threading; + +namespace AutoGen.Core; + +/// +/// The streaming middleware interface. For non-streaming version middleware, check . +/// +public interface IStreamingMiddleware : IMiddleware +{ + /// + /// The streaming version of . + /// + public IAsyncEnumerable InvokeAsync( + MiddlewareContext context, + IStreamingAgent agent, + CancellationToken cancellationToken = default); +} diff --git a/dotnet/src/AutoGen.Core/Middleware/MiddlewareContext.cs b/dotnet/src/AutoGen.Core/Middleware/MiddlewareContext.cs new file mode 100644 index 00000000000..a608d0baf81 --- /dev/null +++ b/dotnet/src/AutoGen.Core/Middleware/MiddlewareContext.cs @@ -0,0 +1,27 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// MiddlewareContext.cs + +using System.Collections.Generic; + +namespace AutoGen.Core; + +public class MiddlewareContext +{ + public MiddlewareContext( + IEnumerable messages, + GenerateReplyOptions? options) + { + this.Messages = messages; + this.Options = options; + } + + /// + /// Messages to send to the agent + /// + public IEnumerable Messages { get; } + + /// + /// Options to generate the reply + /// + public GenerateReplyOptions? Options { get; } +} diff --git a/dotnet/src/AutoGen.Core/Middleware/PrintMessageMiddleware.cs b/dotnet/src/AutoGen.Core/Middleware/PrintMessageMiddleware.cs new file mode 100644 index 00000000000..099f78e5f17 --- /dev/null +++ b/dotnet/src/AutoGen.Core/Middleware/PrintMessageMiddleware.cs @@ -0,0 +1,118 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// PrintMessageMiddleware.cs + +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +namespace AutoGen.Core; + +/// +/// The middleware that prints the reply from agent to the console. +/// +public class PrintMessageMiddleware : IStreamingMiddleware +{ + public string? Name => nameof(PrintMessageMiddleware); + + public async Task InvokeAsync(MiddlewareContext context, IAgent agent, CancellationToken cancellationToken = default) + { + if (agent is IStreamingAgent streamingAgent) + { + IMessage? recentUpdate = null; + await foreach (var message in this.InvokeAsync(context, streamingAgent, cancellationToken)) + { + if (message is IMessage imessage) + { + recentUpdate = imessage; + } + } + Console.WriteLine(); + if (recentUpdate is not null && recentUpdate is not TextMessage) + { + Console.WriteLine(recentUpdate.FormatMessage()); + } + + return recentUpdate ?? throw new InvalidOperationException("The message is not a valid message"); + } + else + { + var reply = await agent.GenerateReplyAsync(context.Messages, context.Options, cancellationToken); + + var formattedMessages = reply.FormatMessage(); + + Console.WriteLine(formattedMessages); + + return reply; + } + } + + public async IAsyncEnumerable InvokeAsync(MiddlewareContext context, IStreamingAgent agent, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + IMessage? recentUpdate = null; + await foreach (var message in agent.GenerateStreamingReplyAsync(context.Messages, context.Options, cancellationToken)) + { + if (message is TextMessageUpdate textMessageUpdate) + { + if (recentUpdate is null) + { + // Print from: xxx + Console.WriteLine($"from: {textMessageUpdate.From}"); + recentUpdate = new TextMessage(textMessageUpdate); + Console.Write(textMessageUpdate.Content); + + yield return message; + } + else if (recentUpdate is TextMessage recentTextMessage) + { + // Print the content of the message + Console.Write(textMessageUpdate.Content); + recentTextMessage.Update(textMessageUpdate); + + yield return recentTextMessage; + } + else + { + throw new InvalidOperationException("The recent update is not a TextMessage"); + } + } + else if (message is ToolCallMessageUpdate toolCallUpdate) + { + if (recentUpdate is null) + { + recentUpdate = new ToolCallMessage(toolCallUpdate); + + yield return message; + } + else if (recentUpdate is ToolCallMessage recentToolCallMessage) + { + recentToolCallMessage.Update(toolCallUpdate); + + yield return message; + } + else + { + throw new InvalidOperationException("The recent update is not a ToolCallMessage"); + } + } + else if (message is IMessage imessage) + { + recentUpdate = imessage; + + yield return imessage; + } + else + { + throw new InvalidOperationException("The message is not a valid message"); + } + } + Console.WriteLine(); + if (recentUpdate is not null && recentUpdate is not TextMessage) + { + Console.WriteLine(recentUpdate.FormatMessage()); + } + + yield return recentUpdate ?? throw new InvalidOperationException("The message is not a valid message"); + } +} diff --git a/dotnet/src/AutoGen.DotnetInteractive/AutoGen.DotnetInteractive.csproj b/dotnet/src/AutoGen.DotnetInteractive/AutoGen.DotnetInteractive.csproj new file mode 100644 index 00000000000..72c67fe7801 --- /dev/null +++ b/dotnet/src/AutoGen.DotnetInteractive/AutoGen.DotnetInteractive.csproj @@ -0,0 +1,38 @@ +ο»Ώ + + + netstandard2.0 + enable + enable + AutoGen.DotnetInteractive + true + + + + + + + AutoGen.DotnetInteractive + + Dotnet interactive integration for AutoGen agents + + + + + + + + + + + + + + + + + + + + + diff --git a/dotnet/src/AutoGen.DotnetInteractive/DotnetInteractiveFunction.cs b/dotnet/src/AutoGen.DotnetInteractive/DotnetInteractiveFunction.cs new file mode 100644 index 00000000000..bb5504cd548 --- /dev/null +++ b/dotnet/src/AutoGen.DotnetInteractive/DotnetInteractiveFunction.cs @@ -0,0 +1,279 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// DotnetInteractiveFunction.cs + +using System.Text; +using System.Text.Json; +using Azure.AI.OpenAI; +using Microsoft.DotNet.Interactive.Documents; +using Microsoft.DotNet.Interactive.Documents.Jupyter; + +namespace AutoGen.DotnetInteractive; + +public class DotnetInteractiveFunction : IDisposable +{ + private readonly InteractiveService? _interactiveService = null; + private string _notebookPath; + private readonly KernelInfoCollection _kernelInfoCollection = new KernelInfoCollection(); + + /// + /// Create an instance of " + /// + /// interactive service to use. + /// notebook path if provided. + public DotnetInteractiveFunction(InteractiveService interactiveService, string? notebookPath = null, bool continueFromExistingNotebook = false) + { + this._interactiveService = interactiveService; + this._notebookPath = notebookPath ?? Path.GetTempPath() + "notebook.ipynb"; + this._kernelInfoCollection.Add(new KernelInfo("csharp")); + this._kernelInfoCollection.Add(new KernelInfo("markdown")); + if (continueFromExistingNotebook == false) + { + // remove existing notebook + if (File.Exists(this._notebookPath)) + { + File.Delete(this._notebookPath); + } + + var document = new InteractiveDocument(); + + using var stream = File.OpenWrite(_notebookPath); + Notebook.Write(document, stream, this._kernelInfoCollection); + stream.Flush(); + stream.Dispose(); + } + else if (continueFromExistingNotebook == true && File.Exists(this._notebookPath)) + { + // load existing notebook + using var readStream = File.OpenRead(this._notebookPath); + var document = Notebook.Read(readStream, this._kernelInfoCollection); + foreach (var cell in document.Elements) + { + if (cell.KernelName == "csharp") + { + var code = cell.Contents; + this._interactiveService.SubmitCSharpCodeAsync(code, default).Wait(); + } + } + } + else + { + // create an empty notebook + var document = new InteractiveDocument(); + + using var stream = File.OpenWrite(_notebookPath); + Notebook.Write(document, stream, this._kernelInfoCollection); + stream.Flush(); + stream.Dispose(); + } + } + + /// + /// Run existing dotnet code from message. Don't modify the code, run it as is. + /// + /// code. + public async Task RunCode(string code) + { + if (this._interactiveService == null) + { + throw new Exception("InteractiveService is not initialized."); + } + + var result = await this._interactiveService.SubmitCSharpCodeAsync(code, default); + if (result != null) + { + // if result contains Error, return entire message + if (result.StartsWith("Error:")) + { + return result; + } + + // add cell if _notebookPath is not null + if (this._notebookPath != null) + { + await AddCellAsync(code, "csharp"); + } + + // if result is over 100 characters, only return the first 100 characters. + if (result.Length > 100) + { + result = result.Substring(0, 100) + " (...too long to present)"; + + return result; + } + + return result; + } + + // add cell if _notebookPath is not null + if (this._notebookPath != null) + { + await AddCellAsync(code, "csharp"); + } + + return "Code run successfully. no output is available."; + } + + /// + /// Install nuget packages. + /// + /// nuget package to install. + public async Task InstallNugetPackages(string[] nugetPackages) + { + if (this._interactiveService == null) + { + throw new Exception("InteractiveService is not initialized."); + } + + var codeSB = new StringBuilder(); + foreach (var nuget in nugetPackages ?? Array.Empty()) + { + var nugetInstallCommand = $"#r \"nuget:{nuget}\""; + codeSB.AppendLine(nugetInstallCommand); + await this._interactiveService.SubmitCSharpCodeAsync(nugetInstallCommand, default); + } + + var code = codeSB.ToString(); + if (this._notebookPath != null) + { + await AddCellAsync(code, "csharp"); + } + + var sb = new StringBuilder(); + sb.AppendLine("Installed nuget packages:"); + foreach (var nuget in nugetPackages ?? Array.Empty()) + { + sb.AppendLine($"- {nuget}"); + } + + return sb.ToString(); + } + + private async Task AddCellAsync(string cellContent, string kernelName) + { + if (!File.Exists(this._notebookPath)) + { + using var stream = File.OpenWrite(this._notebookPath); + Notebook.Write(new InteractiveDocument(), stream, this._kernelInfoCollection); + stream.Dispose(); + } + + using var readStream = File.OpenRead(this._notebookPath); + var document = Notebook.Read(readStream, this._kernelInfoCollection); + readStream.Dispose(); + + var cell = new InteractiveDocumentElement(cellContent, kernelName); + + document.Add(cell); + + using var writeStream = File.OpenWrite(this._notebookPath); + Notebook.Write(document, writeStream, this._kernelInfoCollection); + // sleep 3 seconds + await Task.Delay(3000); + writeStream.Flush(); + writeStream.Dispose(); + } + + private class RunCodeSchema + { + public string code { get; set; } = string.Empty; + } + + public Task RunCodeWrapper(string arguments) + { + var schema = JsonSerializer.Deserialize( + arguments, + new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + }); + + return RunCode(schema!.code); + } + + public FunctionDefinition RunCodeFunction + { + get => new FunctionDefinition + { + Name = @"RunCode", + Description = """ +Run existing dotnet code from message. Don't modify the code, run it as is. +""", + Parameters = BinaryData.FromObjectAsJson(new + { + Type = "object", + Properties = new + { + code = new + { + Type = @"string", + Description = @"code.", + }, + }, + Required = new[] + { + "code", + }, + }, + new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + }) + }; + } + + private class InstallNugetPackagesSchema + { + public string[] nugetPackages { get; set; } = Array.Empty(); + } + + public Task InstallNugetPackagesWrapper(string arguments) + { + var schema = JsonSerializer.Deserialize( + arguments, + new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + }); + + return InstallNugetPackages(schema!.nugetPackages); + } + + public FunctionDefinition InstallNugetPackagesFunction + { + get => new FunctionDefinition + { + Name = @"InstallNugetPackages", + Description = """ +Install nuget packages. +""", + Parameters = BinaryData.FromObjectAsJson(new + { + Type = "object", + Properties = new + { + nugetPackages = new + { + Type = @"array", + Items = new + { + Type = @"string", + }, + Description = @"nuget package to install.", + }, + }, + Required = new[] + { + "nugetPackages", + }, + }, + new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + }) + }; + } + public void Dispose() + { + this._interactiveService?.Dispose(); + } +} diff --git a/dotnet/src/AutoGen.DotnetInteractive/Extension/AgentExtension.cs b/dotnet/src/AutoGen.DotnetInteractive/Extension/AgentExtension.cs new file mode 100644 index 00000000000..83955c53fa1 --- /dev/null +++ b/dotnet/src/AutoGen.DotnetInteractive/Extension/AgentExtension.cs @@ -0,0 +1,83 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// AgentExtension.cs + +using System.Text; +namespace AutoGen.DotnetInteractive; + +public static class AgentExtension +{ + /// + /// Register an AutoReply hook to run dotnet code block from message. + /// This hook will first detect if there's any dotnet code block (e.g. ```csharp and ```) in the most recent message. + /// if there's any, it will run the code block and send the result back as reply. + /// + /// agent + /// interactive service + /// code block prefix + /// code block suffix + /// maximum output to keep + /// + /// + /// + public static IAgent RegisterDotnetCodeBlockExectionHook( + this IAgent agent, + InteractiveService interactiveService, + string codeBlockPrefix = "```csharp", + string codeBlockSuffix = "```", + int maximumOutputToKeep = 500) + { + return agent.RegisterMiddleware(async (msgs, option, innerAgent, ct) => + { + var lastMessage = msgs.LastOrDefault(); + if (lastMessage == null || lastMessage.GetContent() is null) + { + return await innerAgent.GenerateReplyAsync(msgs, option, ct); + } + + // retrieve all code blocks from last message + var codeBlocks = lastMessage.GetContent()!.Split(new[] { codeBlockPrefix }, StringSplitOptions.RemoveEmptyEntries); + if (codeBlocks.Length <= 0) + { + return await innerAgent.GenerateReplyAsync(msgs, option, ct); + } + + // run code blocks + var result = new StringBuilder(); + var i = 0; + result.AppendLine(@$"// [DOTNET_CODE_BLOCK_EXECUTION]"); + foreach (var codeBlock in codeBlocks) + { + var codeBlockIndex = codeBlock.IndexOf(codeBlockSuffix); + + if (codeBlockIndex == -1) + { + continue; + } + + // remove code block suffix + var code = codeBlock.Substring(0, codeBlockIndex).Trim(); + + if (code.Length == 0) + { + continue; + } + + var codeResult = await interactiveService.SubmitCSharpCodeAsync(code, ct); + if (codeResult != null) + { + result.AppendLine(@$"### Executing result for code block {i++}"); + result.AppendLine(codeResult); + result.AppendLine("### End of executing result ###"); + } + } + if (result.Length <= maximumOutputToKeep) + { + maximumOutputToKeep = result.Length; + } + + return new TextMessage(Role.Assistant, result.ToString().Substring(0, maximumOutputToKeep), from: agent.Name); + }); + } +} diff --git a/dotnet/src/AutoGen.DotnetInteractive/GlobalUsing.cs b/dotnet/src/AutoGen.DotnetInteractive/GlobalUsing.cs new file mode 100644 index 00000000000..d66bf001ed5 --- /dev/null +++ b/dotnet/src/AutoGen.DotnetInteractive/GlobalUsing.cs @@ -0,0 +1,4 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// GlobalUsing.cs + +global using AutoGen.Core; diff --git a/dotnet/src/AutoGen.DotnetInteractive/InteractiveService.cs b/dotnet/src/AutoGen.DotnetInteractive/InteractiveService.cs new file mode 100644 index 00000000000..7490b64e126 --- /dev/null +++ b/dotnet/src/AutoGen.DotnetInteractive/InteractiveService.cs @@ -0,0 +1,260 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// InteractiveService.cs + +using System.Diagnostics; +using System.Reactive.Linq; +using System.Reflection; +using Microsoft.DotNet.Interactive; +using Microsoft.DotNet.Interactive.Commands; +using Microsoft.DotNet.Interactive.Connection; +using Microsoft.DotNet.Interactive.Events; +using Microsoft.DotNet.Interactive.Utility; + +namespace AutoGen.DotnetInteractive; + +public class InteractiveService : IDisposable +{ + private Kernel? kernel = null; + private Process? process = null; + private bool disposedValue; + private const string DotnetInteractiveToolNotInstallMessage = "Cannot find a tool in the manifest file that has a command named 'dotnet-interactive'."; + //private readonly ProcessJobTracker jobTracker = new ProcessJobTracker(); + private string installingDirectory; + + public event EventHandler? DisplayEvent; + + public event EventHandler? Output; + + public event EventHandler? CommandFailed; + + public event EventHandler? HoverTextProduced; + + /// + /// Create an instance of InteractiveService + /// + /// dotnet interactive installing directory + public InteractiveService(string installingDirectory) + { + this.installingDirectory = installingDirectory; + } + + public async Task StartAsync(string workingDirectory, CancellationToken ct = default) + { + this.kernel = await this.CreateKernelAsync(workingDirectory, true, ct); + return true; + } + + public async Task SubmitCommandAsync(KernelCommand cmd, CancellationToken ct) + { + if (this.kernel == null) + { + throw new Exception("Kernel is not running"); + } + + try + { + var res = await this.kernel.SendAndThrowOnCommandFailedAsync(cmd, ct); + var events = res.Events; + var displayValues = events.Where(x => x is StandardErrorValueProduced || x is StandardOutputValueProduced || x is ReturnValueProduced) + .SelectMany(x => (x as DisplayEvent)!.FormattedValues); + + if (displayValues is null || displayValues.Count() == 0) + { + return null; + } + + return string.Join("\n", displayValues.Select(x => x.Value)); + } + catch (Exception ex) + { + return $"Error: {ex.Message}"; + } + } + + public async Task SubmitPowershellCodeAsync(string code, CancellationToken ct) + { + var command = new SubmitCode(code, targetKernelName: "pwsh"); + return await this.SubmitCommandAsync(command, ct); + } + + public async Task SubmitCSharpCodeAsync(string code, CancellationToken ct) + { + var command = new SubmitCode(code, targetKernelName: "csharp"); + return await this.SubmitCommandAsync(command, ct); + } + + public bool RestoreDotnetInteractive() + { + this.WriteLine("Restore dotnet interactive tool"); + // write RestoreInteractive.config from embedded resource to this.workingDirectory + var assembly = Assembly.GetAssembly(typeof(InteractiveService))!; + var resourceName = "AutoGen.DotnetInteractive.RestoreInteractive.config"; + using (var stream = assembly.GetManifestResourceStream(resourceName)!) + using (var fileStream = File.Create(Path.Combine(this.installingDirectory, "RestoreInteractive.config"))) + { + stream.CopyTo(fileStream); + } + + // write dotnet-tool.json from embedded resource to this.workingDirectory + + resourceName = "AutoGen.DotnetInteractive.dotnet-tools.json"; + using (var stream2 = assembly.GetManifestResourceStream(resourceName)!) + using (var fileStream2 = File.Create(Path.Combine(this.installingDirectory, "dotnet-tools.json"))) + { + stream2.CopyTo(fileStream2); + } + + var psi = new ProcessStartInfo + { + FileName = "dotnet", + Arguments = $"tool restore --configfile RestoreInteractive.config", + WorkingDirectory = this.installingDirectory, + RedirectStandardInput = true, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true, + }; + + using var process = new Process { StartInfo = psi }; + process.OutputDataReceived += this.PrintProcessOutput; + process.ErrorDataReceived += this.PrintProcessOutput; + process.Start(); + process.BeginErrorReadLine(); + process.BeginOutputReadLine(); + process.WaitForExit(); + + return process.ExitCode == 0; + } + + private async Task CreateKernelAsync(string workingDirectory, bool restoreWhenFail = true, CancellationToken ct = default) + { + try + { + var url = KernelHost.CreateHostUriForCurrentProcessId(); + var compositeKernel = new CompositeKernel("cbcomposite"); + var cmd = new string[] + { + "dotnet", + "tool", + "run", + "dotnet-interactive", + $"[cb-{Process.GetCurrentProcess().Id}]", + "stdio", + //"--default-kernel", + //"csharp", + "--working-dir", + $@"""{workingDirectory}""", + }; + var connector = new StdIoKernelConnector( + cmd, + "root-proxy", + url, + new DirectoryInfo(workingDirectory)); + + // Start the dotnet-interactive tool and get a proxy for the root composite kernel therein. + using var rootProxyKernel = await connector.CreateRootProxyKernelAsync().ConfigureAwait(false); + + // Get proxies for each subkernel present inside the dotnet-interactive tool. + var requestKernelInfoCommand = new RequestKernelInfo(rootProxyKernel.KernelInfo.RemoteUri); + var result = + await rootProxyKernel.SendAsync( + requestKernelInfoCommand, + ct).ConfigureAwait(false); + + var subKernels = result.Events.OfType(); + + foreach (var kernelInfoProduced in result.Events.OfType()) + { + var kernelInfo = kernelInfoProduced.KernelInfo; + if (kernelInfo is not null && !kernelInfo.IsProxy && !kernelInfo.IsComposite) + { + var proxyKernel = await connector.CreateProxyKernelAsync(kernelInfo).ConfigureAwait(false); + proxyKernel.SetUpValueSharingIfSupported(); + compositeKernel.Add(proxyKernel); + } + } + + //compositeKernel.DefaultKernelName = "csharp"; + compositeKernel.Add(rootProxyKernel); + + compositeKernel.KernelEvents.Subscribe(this.OnKernelDiagnosticEventReceived); + + return compositeKernel; + } + catch (CommandLineInvocationException) when (restoreWhenFail) + { + var success = this.RestoreDotnetInteractive(); + + if (success) + { + return await this.CreateKernelAsync(workingDirectory, false, ct); + } + + throw; + } + } + + private void OnKernelDiagnosticEventReceived(KernelEvent ke) + { + this.WriteLine("Receive data from kernel"); + this.WriteLine(KernelEventEnvelope.Serialize(ke)); + + switch (ke) + { + case DisplayEvent de: + this.DisplayEvent?.Invoke(this, de); + break; + case CommandFailed cf: + this.CommandFailed?.Invoke(this, cf); + break; + case HoverTextProduced cf: + this.HoverTextProduced?.Invoke(this, cf); + break; + } + } + + private void WriteLine(string data) + { + this.Output?.Invoke(this, data); + } + + private void PrintProcessOutput(object sender, DataReceivedEventArgs e) + { + if (!string.IsNullOrEmpty(e.Data)) + { + this.WriteLine(e.Data); + } + } + + public bool IsRunning() + { + return this.kernel != null; + } + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + this.kernel?.Dispose(); + + if (this.process != null) + { + this.process.Kill(); + this.process.Dispose(); + } + } + + disposedValue = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } +} diff --git a/dotnet/src/AutoGen.DotnetInteractive/RestoreInteractive.config b/dotnet/src/AutoGen.DotnetInteractive/RestoreInteractive.config new file mode 100644 index 00000000000..390adb4ab6f --- /dev/null +++ b/dotnet/src/AutoGen.DotnetInteractive/RestoreInteractive.config @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/dotnet/src/AutoGen.DotnetInteractive/Utils.cs b/dotnet/src/AutoGen.DotnetInteractive/Utils.cs new file mode 100644 index 00000000000..d10208d508c --- /dev/null +++ b/dotnet/src/AutoGen.DotnetInteractive/Utils.cs @@ -0,0 +1,86 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// Utils.cs + +using System.Collections; +using System.Collections.Immutable; +using Microsoft.DotNet.Interactive; +using Microsoft.DotNet.Interactive.Commands; +using Microsoft.DotNet.Interactive.Connection; +using Microsoft.DotNet.Interactive.Events; + +public static class ObservableExtensions +{ + public static SubscribedList ToSubscribedList(this IObservable source) + { + return new SubscribedList(source); + } +} + +public static class KernelExtensions +{ + internal static void SetUpValueSharingIfSupported(this ProxyKernel proxyKernel) + { + var supportedCommands = proxyKernel.KernelInfo.SupportedKernelCommands; + if (supportedCommands.Any(d => d.Name == nameof(RequestValue)) && + supportedCommands.Any(d => d.Name == nameof(SendValue))) + { + proxyKernel.UseValueSharing(); + } + } + + internal static async Task SendAndThrowOnCommandFailedAsync( + this Kernel kernel, + KernelCommand command, + CancellationToken cancellationToken) + { + var result = await kernel.SendAsync(command, cancellationToken); + result.ThrowOnCommandFailed(); + return result; + } + + private static void ThrowOnCommandFailed(this KernelCommandResult result) + { + var failedEvents = result.Events.OfType(); + if (!failedEvents.Any()) + { + return; + } + + if (failedEvents.Skip(1).Any()) + { + var innerExceptions = failedEvents.Select(f => f.GetException()); + throw new AggregateException(innerExceptions); + } + else + { + throw failedEvents.Single().GetException(); + } + } + + private static Exception GetException(this CommandFailed commandFailedEvent) + => new Exception(commandFailedEvent.Message); +} + +public class SubscribedList : IReadOnlyList, IDisposable +{ + private ImmutableArray _list = ImmutableArray.Empty; + private readonly IDisposable _subscription; + + public SubscribedList(IObservable source) + { + _subscription = source.Subscribe(x => _list = _list.Add(x)); + } + + public IEnumerator GetEnumerator() + { + return ((IEnumerable)_list).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public int Count => _list.Length; + + public T this[int index] => _list[index]; + + public void Dispose() => _subscription.Dispose(); +} diff --git a/dotnet/src/AutoGen.DotnetInteractive/dotnet-tools.json b/dotnet/src/AutoGen.DotnetInteractive/dotnet-tools.json new file mode 100644 index 00000000000..12b09e61cae --- /dev/null +++ b/dotnet/src/AutoGen.DotnetInteractive/dotnet-tools.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "Microsoft.dotnet-interactive": { + "version": "1.0.522904", + "commands": [ + "dotnet-interactive" + ] + } + } +} \ No newline at end of file diff --git a/dotnet/src/AutoGen.LMStudio/AutoGen.LMStudio.csproj b/dotnet/src/AutoGen.LMStudio/AutoGen.LMStudio.csproj new file mode 100644 index 00000000000..f45a2f7eba5 --- /dev/null +++ b/dotnet/src/AutoGen.LMStudio/AutoGen.LMStudio.csproj @@ -0,0 +1,23 @@ +ο»Ώ + + + netstandard2.0 + AutoGen.LMStudio + + + + + + + AutoGen.LMStudio + + Provide support for consuming LMStudio openai-like API service in AutoGen + + + + + + + + + diff --git a/dotnet/src/AutoGen.LMStudio/GlobalUsing.cs b/dotnet/src/AutoGen.LMStudio/GlobalUsing.cs new file mode 100644 index 00000000000..d66bf001ed5 --- /dev/null +++ b/dotnet/src/AutoGen.LMStudio/GlobalUsing.cs @@ -0,0 +1,4 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// GlobalUsing.cs + +global using AutoGen.Core; diff --git a/dotnet/src/AutoGen.LMStudio/LMStudioAgent.cs b/dotnet/src/AutoGen.LMStudio/LMStudioAgent.cs new file mode 100644 index 00000000000..9d0daa535b2 --- /dev/null +++ b/dotnet/src/AutoGen.LMStudio/LMStudioAgent.cs @@ -0,0 +1,88 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// LMStudioAgent.cs + +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using AutoGen.OpenAI; +using Azure.AI.OpenAI; +using Azure.Core.Pipeline; + +namespace AutoGen.LMStudio; + +/// +/// agent that consumes local server from LM Studio +/// +/// +/// [!code-csharp[LMStudioAgent](../../sample/AutoGen.BasicSamples/Example08_LMStudio.cs?name=lmstudio_example_1)] +/// +public class LMStudioAgent : IAgent +{ + private readonly GPTAgent innerAgent; + + public LMStudioAgent( + string name, + LMStudioConfig config, + string systemMessage = "You are a helpful AI assistant", + float temperature = 0.7f, + int maxTokens = 1024, + IEnumerable? functions = null, + IDictionary>>? functionMap = null) + { + var client = ConfigOpenAIClientForLMStudio(config); + innerAgent = new GPTAgent( + name: name, + systemMessage: systemMessage, + openAIClient: client, + modelName: "llm", // model name doesn't matter for LM Studio + temperature: temperature, + maxTokens: maxTokens, + functions: functions, + functionMap: functionMap); + } + + public string Name => innerAgent.Name; + + public Task GenerateReplyAsync( + IEnumerable messages, + GenerateReplyOptions? options = null, + System.Threading.CancellationToken cancellationToken = default) + { + return innerAgent.GenerateReplyAsync(messages, options, cancellationToken); + } + + private OpenAIClient ConfigOpenAIClientForLMStudio(LMStudioConfig config) + { + // create uri from host and port + var uri = config.Uri; + var handler = new CustomHttpClientHandler(uri); + var httpClient = new HttpClient(handler); + var option = new OpenAIClientOptions(OpenAIClientOptions.ServiceVersion.V2022_12_01) + { + Transport = new HttpClientTransport(httpClient), + }; + + return new OpenAIClient("api-key", option); + } + + private sealed class CustomHttpClientHandler : HttpClientHandler + { + private Uri _modelServiceUrl; + + public CustomHttpClientHandler(Uri modelServiceUrl) + { + _modelServiceUrl = modelServiceUrl; + } + + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + // request.RequestUri = new Uri($"{_modelServiceUrl}{request.RequestUri.PathAndQuery}"); + var uriBuilder = new UriBuilder(_modelServiceUrl); + uriBuilder.Path = request.RequestUri.PathAndQuery; + request.RequestUri = uriBuilder.Uri; + return base.SendAsync(request, cancellationToken); + } + } +} diff --git a/dotnet/src/AutoGen.LMStudio/LMStudioConfig.cs b/dotnet/src/AutoGen.LMStudio/LMStudioConfig.cs new file mode 100644 index 00000000000..5a359fd74e9 --- /dev/null +++ b/dotnet/src/AutoGen.LMStudio/LMStudioConfig.cs @@ -0,0 +1,30 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// LMStudioConfig.cs + +using System; + +/// +/// Add support for consuming openai-like API from LM Studio +/// +public class LMStudioConfig : ILLMConfig +{ + public LMStudioConfig(string host, int port) + { + this.Host = host; + this.Port = port; + this.Uri = new Uri($"http://{host}:{port}"); + } + + public LMStudioConfig(Uri uri) + { + this.Uri = uri; + this.Host = uri.Host; + this.Port = uri.Port; + } + + public string Host { get; } + + public int Port { get; } + + public Uri Uri { get; } +} diff --git a/dotnet/src/AutoGen.LMStudio/README.md b/dotnet/src/AutoGen.LMStudio/README.md new file mode 100644 index 00000000000..1e5caf4756c --- /dev/null +++ b/dotnet/src/AutoGen.LMStudio/README.md @@ -0,0 +1,31 @@ +## AutoGen.LMStudio + +This package provides support for consuming openai-like API from LMStudio local server. + +## Installation +To use `AutoGen.LMStudio`, add the following package to your `.csproj` file: + +```xml + + + +``` + +## Usage +```csharp +using AutoGen.LMStudio; +var localServerEndpoint = "localhost"; +var port = 5000; +var lmStudioConfig = new LMStudioConfig(localServerEndpoint, port); +var agent = new LMStudioAgent( + name: "agent", + systemMessage: "You are an agent that help user to do some tasks.", + lmStudioConfig: lmStudioConfig) + .RegisterPrintMessage(); // register a hook to print message nicely to console + +await agent.SendAsync("Can you write a piece of C# code to calculate 100th of fibonacci?"); +``` + +## Update history +### Update on 0.0.7 (2024-02-11) +- Add `LMStudioAgent` to support consuming openai-like API from LMStudio local server. diff --git a/dotnet/src/AutoGen.Mistral/Agent/MistralClientAgent.cs b/dotnet/src/AutoGen.Mistral/Agent/MistralClientAgent.cs new file mode 100644 index 00000000000..cc2c7414550 --- /dev/null +++ b/dotnet/src/AutoGen.Mistral/Agent/MistralClientAgent.cs @@ -0,0 +1,129 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// MistralClientAgent.cs + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using AutoGen.Core; +using AutoGen.Mistral.Extension; + +namespace AutoGen.Mistral; + +/// +/// Mistral client agent. +/// +/// This agent supports the following input message types: +/// +/// where T is +/// +/// +/// This agent returns the following message types: +/// +/// where T is +/// +/// +/// You can register this agent with +/// to support more AutoGen message types. +/// +public class MistralClientAgent : IStreamingAgent +{ + private readonly MistralClient _client; + private readonly string _systemMessage; + private readonly string _model; + private readonly int? _randomSeed; + private readonly bool _jsonOutput = false; + private ToolChoiceEnum? _toolChoice; + + /// + /// Create a new instance of . + /// + /// + /// the name of this agent + /// the mistral model id. + /// system message. + /// the seed to generate output. + /// tool choice strategy. + /// use json output. + public MistralClientAgent( + MistralClient client, + string name, + string model, + string systemMessage = "You are a helpful AI assistant", + int? randomSeed = null, + ToolChoiceEnum? toolChoice = null, + bool jsonOutput = false) + { + _client = client; + Name = name; + _systemMessage = systemMessage; + _model = model; + _randomSeed = randomSeed; + _jsonOutput = jsonOutput; + _toolChoice = toolChoice; + } + + public string Name { get; } + + public async Task GenerateReplyAsync( + IEnumerable messages, + GenerateReplyOptions? options = null, + CancellationToken cancellationToken = default) + { + var request = BuildChatRequest(messages, options); + var response = await _client.CreateChatCompletionsAsync(request); + + return new MessageEnvelope(response, from: this.Name); + } + + public async IAsyncEnumerable GenerateStreamingReplyAsync( + IEnumerable messages, + GenerateReplyOptions? options = null, + [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + var request = BuildChatRequest(messages, options); + var response = _client.StreamingChatCompletionsAsync(request); + + await foreach (var content in response) + { + yield return new MessageEnvelope(content, from: this.Name); + } + } + + private ChatCompletionRequest BuildChatRequest(IEnumerable messages, GenerateReplyOptions? options) + { + var chatHistory = BuildChatHistory(messages); + var chatRequest = new ChatCompletionRequest(model: _model, messages: chatHistory.ToList(), temperature: options?.Temperature, randomSeed: _randomSeed) + { + MaxTokens = options?.MaxToken, + ResponseFormat = _jsonOutput ? new ResponseFormat() { ResponseFormatType = "json_object" } : null, + }; + + if (options?.Functions != null) + { + chatRequest.Tools = options.Functions.Select(f => new FunctionTool(f.ToMistralFunctionDefinition())).ToList(); + chatRequest.ToolChoice = _toolChoice ?? ToolChoiceEnum.Auto; + } + + return chatRequest; + } + + private IEnumerable BuildChatHistory(IEnumerable messages) + { + var history = messages.Select(m => m switch + { + IMessage chatMessage => chatMessage.Content, + _ => throw new ArgumentException("Invalid message type") + }); + + // if there's no system message in the history, add one to the beginning + if (!history.Any(m => m.Role == ChatMessage.RoleEnum.System)) + { + history = new[] { new ChatMessage(ChatMessage.RoleEnum.System, _systemMessage) }.Concat(history); + } + + return history; + } +} diff --git a/dotnet/src/AutoGen.Mistral/AutoGen.Mistral.csproj b/dotnet/src/AutoGen.Mistral/AutoGen.Mistral.csproj new file mode 100644 index 00000000000..f1bb8e0afab --- /dev/null +++ b/dotnet/src/AutoGen.Mistral/AutoGen.Mistral.csproj @@ -0,0 +1,27 @@ +ο»Ώ + + + netstandard2.0 + AutoGen.Mistral + + + + + + + AutoGen.Mistral + + Provide support for consuming Mistral model in AutoGen + + + + + + + + + + + + + diff --git a/dotnet/src/AutoGen.Mistral/Converters/JsonPropertyNameEnumConverter.cs b/dotnet/src/AutoGen.Mistral/Converters/JsonPropertyNameEnumConverter.cs new file mode 100644 index 00000000000..5a4f9f9cb18 --- /dev/null +++ b/dotnet/src/AutoGen.Mistral/Converters/JsonPropertyNameEnumConverter.cs @@ -0,0 +1,43 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// JsonPropertyNameEnumConverter.cs + +using System; +using System.Reflection; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace AutoGen.Mistral; + +internal class JsonPropertyNameEnumConverter : JsonConverter where T : struct, Enum +{ + public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + string value = reader.GetString() ?? throw new JsonException("Value was null."); + + foreach (var field in typeToConvert.GetFields()) + { + var attribute = field.GetCustomAttribute(); + if (attribute?.Name == value) + { + return (T)Enum.Parse(typeToConvert, field.Name); + } + } + + throw new JsonException($"Unable to convert \"{value}\" to enum {typeToConvert}."); + } + + public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) + { + var field = value.GetType().GetField(value.ToString()); + var attribute = field.GetCustomAttribute(); + + if (attribute != null) + { + writer.WriteStringValue(attribute.Name); + } + else + { + writer.WriteStringValue(value.ToString()); + } + } +} diff --git a/dotnet/src/AutoGen.Mistral/DTOs/ChatCompletionRequest.cs b/dotnet/src/AutoGen.Mistral/DTOs/ChatCompletionRequest.cs new file mode 100644 index 00000000000..71a084673f1 --- /dev/null +++ b/dotnet/src/AutoGen.Mistral/DTOs/ChatCompletionRequest.cs @@ -0,0 +1,116 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// ChatCompletionRequest.cs + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace AutoGen.Mistral; + +public class ChatCompletionRequest +{ + /// + /// Initializes a new instance of the class. + /// + /// ID of the model to use. You can use the [List Available Models](/api#operation/listModels) API to see all of your available models, or see our [Model overview](/models) for model descriptions. (required). + /// The prompt(s) to generate completions for, encoded as a list of dict with role and content. The first prompt role should be `user` or `system`. (required). + /// What sampling temperature to use, between 0.0 and 1.0. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. We generally recommend altering this or `top_p` but not both. (default to 0.7M). + /// Nucleus sampling, where the model considers the results of the tokens with `top_p` probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered. We generally recommend altering this or `temperature` but not both. (default to 1M). + /// The maximum number of tokens to generate in the completion. The token count of your prompt plus `max_tokens` cannot exceed the model's context length. . + /// Whether to stream back partial progress. If set, tokens will be sent as data-only server-sent events as they become available, with the stream terminated by a data: [DONE] message. Otherwise, the server will hold the request open until the timeout or until completion, with the response containing the full result as JSON. (default to false). + /// Whether to inject a safety prompt before all conversations. (default to false). + /// The seed to use for random sampling. If set, different calls will generate deterministic results. . + public ChatCompletionRequest(string? model = default(string), List? messages = default(List), float? temperature = 0.7f, float? topP = 1f, int? maxTokens = default(int?), bool? stream = false, bool safePrompt = false, int? randomSeed = default(int?)) + { + // to ensure "model" is required (not null) + if (model == null) + { + throw new ArgumentNullException("model is a required property for ChatCompletionRequest and cannot be null"); + } + this.Model = model; + // to ensure "messages" is required (not null) + if (messages == null) + { + throw new ArgumentNullException("messages is a required property for ChatCompletionRequest and cannot be null"); + } + this.Messages = messages; + // use default value if no "temperature" provided + this.Temperature = temperature ?? 0.7f; + // use default value if no "topP" provided + this.TopP = topP ?? 1f; + this.MaxTokens = maxTokens; + // use default value if no "stream" provided + this.Stream = stream ?? false; + this.SafePrompt = safePrompt; + this.RandomSeed = randomSeed; + } + /// + /// ID of the model to use. You can use the [List Available Models](/api#operation/listModels) API to see all of your available models, or see our [Model overview](/models) for model descriptions. + /// + /// ID of the model to use. You can use the [List Available Models](/api#operation/listModels) API to see all of your available models, or see our [Model overview](/models) for model descriptions. + /// mistral-tiny + [JsonPropertyName("model")] + public string Model { get; set; } + + /// + /// The prompt(s) to generate completions for, encoded as a list of dict with role and content. The first prompt role should be `user` or `system`. + /// + /// The prompt(s) to generate completions for, encoded as a list of dict with role and content. The first prompt role should be `user` or `system`. + /// [{"role":"user","content":"What is the best French cheese?"}] + [JsonPropertyName("messages")] + public List Messages { get; set; } + + /// + /// What sampling temperature to use, between 0.0 and 1.0. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. We generally recommend altering this or `top_p` but not both. + /// + /// What sampling temperature to use, between 0.0 and 1.0. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. We generally recommend altering this or `top_p` but not both. + /// 0.7 + [JsonPropertyName("temperature")] + public float? Temperature { get; set; } + + /// + /// Nucleus sampling, where the model considers the results of the tokens with `top_p` probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered. We generally recommend altering this or `temperature` but not both. + /// + /// Nucleus sampling, where the model considers the results of the tokens with `top_p` probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered. We generally recommend altering this or `temperature` but not both. + /// 1 + [JsonPropertyName("top_p")] + public float? TopP { get; set; } + + /// + /// The maximum number of tokens to generate in the completion. The token count of your prompt plus `max_tokens` cannot exceed the model's context length. + /// + /// The maximum number of tokens to generate in the completion. The token count of your prompt plus `max_tokens` cannot exceed the model's context length. + /// 16 + [JsonPropertyName("max_tokens")] + public int? MaxTokens { get; set; } + + /// + /// Whether to stream back partial progress. If set, tokens will be sent as data-only server-sent events as they become available, with the stream terminated by a data: [DONE] message. Otherwise, the server will hold the request open until the timeout or until completion, with the response containing the full result as JSON. + /// + /// Whether to stream back partial progress. If set, tokens will be sent as data-only server-sent events as they become available, with the stream terminated by a data: [DONE] message. Otherwise, the server will hold the request open until the timeout or until completion, with the response containing the full result as JSON. + [JsonPropertyName("stream")] + public bool? Stream { get; set; } + + /// + /// Whether to inject a safety prompt before all conversations. + /// + /// Whether to inject a safety prompt before all conversations. + [JsonPropertyName("safe_prompt")] + public bool SafePrompt { get; set; } + + /// + /// The seed to use for random sampling. If set, different calls will generate deterministic results. + /// + /// The seed to use for random sampling. If set, different calls will generate deterministic results. + [JsonPropertyName("random_seed")] + public int? RandomSeed { get; set; } + + [JsonPropertyName("tools")] + public List? Tools { get; set; } + + [JsonPropertyName("tool_choice")] + public ToolChoiceEnum? ToolChoice { get; set; } + + [JsonPropertyName("response_format")] + public ResponseFormat? ResponseFormat { get; set; } = null; +} diff --git a/dotnet/src/AutoGen.Mistral/DTOs/ChatCompletionResponse.cs b/dotnet/src/AutoGen.Mistral/DTOs/ChatCompletionResponse.cs new file mode 100644 index 00000000000..ff241f8d340 --- /dev/null +++ b/dotnet/src/AutoGen.Mistral/DTOs/ChatCompletionResponse.cs @@ -0,0 +1,50 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// ChatCompletionResponse.cs + +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace AutoGen.Mistral; + +public class ChatCompletionResponse +{ + /// + /// Gets or Sets Id + /// + /// cmpl-e5cc70bb28c444948073e77776eb30ef + [JsonPropertyName("id")] + public string? Id { get; set; } + + /// + /// Gets or Sets VarObject + /// + /// chat.completion + [JsonPropertyName("object")] + public string? VarObject { get; set; } + + /// + /// Gets or Sets Created + /// + /// 1702256327 + [JsonPropertyName("created")] + public int Created { get; set; } + + /// + /// Gets or Sets Model + /// + /// mistral-tiny + [JsonPropertyName("model")] + public string? Model { get; set; } + + /// + /// Gets or Sets Choices + /// + [JsonPropertyName("choices")] + public List? Choices { get; set; } + + /// + /// Gets or Sets Usage + /// + [JsonPropertyName("usage")] + public Usage? Usage { get; set; } +} diff --git a/dotnet/src/AutoGen.Mistral/DTOs/ChatMessage.cs b/dotnet/src/AutoGen.Mistral/DTOs/ChatMessage.cs new file mode 100644 index 00000000000..c5dae2aa34d --- /dev/null +++ b/dotnet/src/AutoGen.Mistral/DTOs/ChatMessage.cs @@ -0,0 +1,96 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// ChatMessage.cs + +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace AutoGen.Mistral; + +public class ChatMessage +{ + /// + /// Initializes a new instance of the class. + /// + /// role. + /// content. + public ChatMessage(RoleEnum? role = default(RoleEnum?), string? content = null) + { + this.Role = role; + this.Content = content; + } + + [JsonConverter(typeof(JsonPropertyNameEnumConverter))] + public enum RoleEnum + { + /// + /// Enum System for value: system + /// + [JsonPropertyName("system")] + //[EnumMember(Value = "system")] + System = 1, + + /// + /// Enum User for value: user + /// + [JsonPropertyName("user")] + //[EnumMember(Value = "user")] + User = 2, + + /// + /// Enum Assistant for value: assistant + /// + [JsonPropertyName("assistant")] + //[EnumMember(Value = "assistant")] + Assistant = 3, + + [JsonPropertyName("tool")] + Tool = 4, + } + + /// + /// Gets or Sets Role + /// + [JsonPropertyName("role")] + public RoleEnum? Role { get; set; } + + /// + /// Gets or Sets Content + /// + [JsonPropertyName("content")] + public string? Content { get; set; } + + /// + /// Gets or Sets name for tool calls + /// + [JsonPropertyName("name")] + public string? Name { get; set; } + + [JsonPropertyName("tool_calls")] + public List? ToolCalls { get; set; } +} + +public class FunctionContent +{ + public FunctionContent(FunctionCall function) + { + this.Function = function; + } + + [JsonPropertyName("function")] + public FunctionCall Function { get; set; } + + public class FunctionCall + { + public FunctionCall(string name, string arguments) + { + this.Name = name; + this.Arguments = arguments; + } + + [JsonPropertyName("name")] + public string Name { get; set; } + + [JsonPropertyName("arguments")] + public string Arguments { get; set; } + } +} diff --git a/dotnet/src/AutoGen.Mistral/DTOs/Choice.cs b/dotnet/src/AutoGen.Mistral/DTOs/Choice.cs new file mode 100644 index 00000000000..ef874c90a0e --- /dev/null +++ b/dotnet/src/AutoGen.Mistral/DTOs/Choice.cs @@ -0,0 +1,58 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// Choice.cs + +using System.Text.Json.Serialization; + +namespace AutoGen.Mistral; + +public class Choice +{ + [JsonConverter(typeof(JsonPropertyNameEnumConverter))] + public enum FinishReasonEnum + { + /// + /// Enum Stop for value: stop + /// + [JsonPropertyName("stop")] + Stop = 1, + + /// + /// Enum Length for value: length + /// + [JsonPropertyName("length")] + Length = 2, + + /// + /// Enum ModelLength for value: model_length + /// + [JsonPropertyName("model_length")] + ModelLength = 3, + + [JsonPropertyName("error")] + Error = 4, + + [JsonPropertyName("tool_calls")] + ToolCalls = 5, + } + + /// + /// Gets or Sets FinishReason + /// + [JsonPropertyName("finish_reason")] + public FinishReasonEnum? FinishReason { get; set; } + + [JsonPropertyName("index")] + public int Index { get; set; } + + /// + /// Gets or Sets Message + /// + [JsonPropertyName("message")] + public ChatMessage? Message { get; set; } + + /// + /// Gets or Sets Delta + /// + [JsonPropertyName("delta")] + public ChatMessage? Delta { get; set; } +} diff --git a/dotnet/src/AutoGen.Mistral/DTOs/Error.cs b/dotnet/src/AutoGen.Mistral/DTOs/Error.cs new file mode 100644 index 00000000000..77eb2d341fb --- /dev/null +++ b/dotnet/src/AutoGen.Mistral/DTOs/Error.cs @@ -0,0 +1,39 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// Error.cs + +using System.Text.Json.Serialization; + +namespace AutoGen.Mistral +{ + public class Error + { + public Error(string type, string message, string? param = default(string), string? code = default(string)) + { + Type = type; + Message = message; + Param = param; + Code = code; + } + + [JsonPropertyName("type")] + public string Type { get; set; } + + /// + /// Gets or Sets Message + /// + [JsonPropertyName("message")] + public string Message { get; set; } + + /// + /// Gets or Sets Param + /// + [JsonPropertyName("param")] + public string? Param { get; set; } + + /// + /// Gets or Sets Code + /// + [JsonPropertyName("code")] + public string? Code { get; set; } + } +} diff --git a/dotnet/src/AutoGen.Mistral/DTOs/ErrorResponse.cs b/dotnet/src/AutoGen.Mistral/DTOs/ErrorResponse.cs new file mode 100644 index 00000000000..ea3a999cc08 --- /dev/null +++ b/dotnet/src/AutoGen.Mistral/DTOs/ErrorResponse.cs @@ -0,0 +1,19 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// ErrorResponse.cs + +using System.Text.Json.Serialization; + +namespace AutoGen.Mistral; + +public class ErrorResponse +{ + public ErrorResponse(Error error) + { + Error = error; + } + /// + /// Gets or Sets Error + /// + [JsonPropertyName("error")] + public Error Error { get; set; } +} diff --git a/dotnet/src/AutoGen.Mistral/DTOs/FunctionDefinition.cs b/dotnet/src/AutoGen.Mistral/DTOs/FunctionDefinition.cs new file mode 100644 index 00000000000..663920330a2 --- /dev/null +++ b/dotnet/src/AutoGen.Mistral/DTOs/FunctionDefinition.cs @@ -0,0 +1,26 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// FunctionDefinition.cs + +using System.Text.Json.Serialization; +using Json.Schema; + +namespace AutoGen.Mistral; + +public class FunctionDefinition +{ + public FunctionDefinition(string name, string description, JsonSchema? parameters = default) + { + Name = name; + Description = description; + Parameters = parameters; + } + + [JsonPropertyName("name")] + public string Name { get; set; } + + [JsonPropertyName("description")] + public string Description { get; set; } + + [JsonPropertyName("parameters")] + public JsonSchema? Parameters { get; set; } +} diff --git a/dotnet/src/AutoGen.Mistral/DTOs/Model.cs b/dotnet/src/AutoGen.Mistral/DTOs/Model.cs new file mode 100644 index 00000000000..915d2f737ec --- /dev/null +++ b/dotnet/src/AutoGen.Mistral/DTOs/Model.cs @@ -0,0 +1,64 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// Model.cs + +using System; +using System.Text.Json.Serialization; + +namespace AutoGen.Mistral; + +public class Model +{ + /// + /// Initializes a new instance of the class. + /// + /// id (required). + /// varObject (required). + /// created (required). + /// ownedBy (required). + public Model(string? id = default(string), string? varObject = default(string), int created = default(int), string? ownedBy = default(string)) + { + // to ensure "id" is required (not null) + if (id == null) + { + throw new ArgumentNullException("id is a required property for Model and cannot be null"); + } + this.Id = id; + // to ensure "varObject" is required (not null) + if (varObject == null) + { + throw new ArgumentNullException("varObject is a required property for Model and cannot be null"); + } + this.VarObject = varObject; + this.Created = created; + // to ensure "ownedBy" is required (not null) + if (ownedBy == null) + { + throw new ArgumentNullException("ownedBy is a required property for Model and cannot be null"); + } + this.OwnedBy = ownedBy; + } + + /// + /// Gets or Sets Id + /// + [JsonPropertyName("id")] + public string Id { get; set; } + + /// + /// Gets or Sets VarObject + /// + [JsonPropertyName("object")] + public string VarObject { get; set; } + + /// + /// Gets or Sets Created + /// + [JsonPropertyName("created")] + public int Created { get; set; } + + /// + /// Gets or Sets OwnedBy + /// + [JsonPropertyName("owned_by")] + public string OwnedBy { get; set; } +} diff --git a/dotnet/src/AutoGen.Mistral/DTOs/ResponseFormat.cs b/dotnet/src/AutoGen.Mistral/DTOs/ResponseFormat.cs new file mode 100644 index 00000000000..08a5c7426ea --- /dev/null +++ b/dotnet/src/AutoGen.Mistral/DTOs/ResponseFormat.cs @@ -0,0 +1,12 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// ResponseFormat.cs + +using System.Text.Json.Serialization; + +namespace AutoGen.Mistral; + +public class ResponseFormat +{ + [JsonPropertyName("type")] + public string ResponseFormatType { get; set; } = "json_object"; +} diff --git a/dotnet/src/AutoGen.Mistral/DTOs/Tool.cs b/dotnet/src/AutoGen.Mistral/DTOs/Tool.cs new file mode 100644 index 00000000000..49e1a9b777d --- /dev/null +++ b/dotnet/src/AutoGen.Mistral/DTOs/Tool.cs @@ -0,0 +1,51 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// Tool.cs + +using System.Text.Json.Serialization; + +namespace AutoGen.Mistral; + +public abstract class ToolBase +{ + [JsonPropertyName("type")] + public string Type { get; set; } + + public ToolBase(string type) + { + Type = type; + } +} + +public class FunctionTool : ToolBase +{ + public FunctionTool(FunctionDefinition function) + : base("function") + { + Function = function; + } + + [JsonPropertyName("function")] + public FunctionDefinition Function { get; set; } +} + +[JsonConverter(typeof(JsonPropertyNameEnumConverter))] +public enum ToolChoiceEnum +{ + /// + /// Auto-detect whether to call a function. + /// + [JsonPropertyName("auto")] + Auto = 0, + + /// + /// Won't call a function. + /// + [JsonPropertyName("none")] + None, + + /// + /// Force to call a function. + /// + [JsonPropertyName("any")] + Any, +} diff --git a/dotnet/src/AutoGen.Mistral/DTOs/Usage.cs b/dotnet/src/AutoGen.Mistral/DTOs/Usage.cs new file mode 100644 index 00000000000..3e739e3bc11 --- /dev/null +++ b/dotnet/src/AutoGen.Mistral/DTOs/Usage.cs @@ -0,0 +1,26 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// Usage.cs + +using System.Text.Json.Serialization; + +namespace AutoGen.Mistral; + +public class Usage +{ + [JsonPropertyName("prompt_tokens")] + public int PromptTokens { get; set; } + + /// + /// Gets or Sets CompletionTokens + /// + /// 93 + [JsonPropertyName("completion_tokens")] + public int CompletionTokens { get; set; } + + /// + /// Gets or Sets TotalTokens + /// + /// 107 + [JsonPropertyName("total_tokens")] + public int TotalTokens { get; set; } +} diff --git a/dotnet/src/AutoGen.Mistral/Extension/FunctionContractExtension.cs b/dotnet/src/AutoGen.Mistral/Extension/FunctionContractExtension.cs new file mode 100644 index 00000000000..eb38b32982a --- /dev/null +++ b/dotnet/src/AutoGen.Mistral/Extension/FunctionContractExtension.cs @@ -0,0 +1,59 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// FunctionContractExtension.cs + +using System; +using System.Collections.Generic; +using AutoGen.Core; +using Json.Schema; +using Json.Schema.Generation; + +namespace AutoGen.Mistral.Extension; + +public static class FunctionContractExtension +{ + /// + /// Convert a to a that can be used in funciton call. + /// + /// function contract + /// + public static FunctionDefinition ToMistralFunctionDefinition(this FunctionContract functionContract) + { + var functionDefinition = new FunctionDefinition(functionContract.Name ?? throw new Exception("Function name cannot be null"), functionContract.Description ?? throw new Exception("Function description cannot be null")); + var requiredParameterNames = new List(); + var propertiesSchemas = new Dictionary(); + var propertySchemaBuilder = new JsonSchemaBuilder().Type(SchemaValueType.Object); + foreach (var param in functionContract.Parameters ?? []) + { + if (param.Name is null) + { + throw new InvalidOperationException("Parameter name cannot be null"); + } + + var schemaBuilder = new JsonSchemaBuilder().FromType(param.ParameterType ?? throw new ArgumentNullException(nameof(param.ParameterType))); + if (param.Description != null) + { + schemaBuilder = schemaBuilder.Description(param.Description); + } + + if (param.IsRequired) + { + requiredParameterNames.Add(param.Name); + } + + var schema = schemaBuilder.Build(); + propertiesSchemas[param.Name] = schema; + + } + propertySchemaBuilder = propertySchemaBuilder.Properties(propertiesSchemas); + propertySchemaBuilder = propertySchemaBuilder.Required(requiredParameterNames); + + var option = new System.Text.Json.JsonSerializerOptions() + { + PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase + }; + + functionDefinition.Parameters = propertySchemaBuilder.Build(); + + return functionDefinition; + } +} diff --git a/dotnet/src/AutoGen.Mistral/Extension/MistralAgentExtension.cs b/dotnet/src/AutoGen.Mistral/Extension/MistralAgentExtension.cs new file mode 100644 index 00000000000..787393d067f --- /dev/null +++ b/dotnet/src/AutoGen.Mistral/Extension/MistralAgentExtension.cs @@ -0,0 +1,37 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// MistralAgentExtension.cs + +using AutoGen.Core; + +namespace AutoGen.Mistral.Extension; + +public static class MistralAgentExtension +{ + /// + /// Register a to support more AutoGen message types. + /// + public static MiddlewareStreamingAgent RegisterMessageConnector( + this MistralClientAgent agent, MistralChatMessageConnector? connector = null) + { + if (connector == null) + { + connector = new MistralChatMessageConnector(); + } + + return agent.RegisterStreamingMiddleware(connector); + } + + /// + /// Register a to support more AutoGen message types. + /// + public static MiddlewareStreamingAgent RegisterMessageConnector( + this MiddlewareStreamingAgent agent, MistralChatMessageConnector? connector = null) + { + if (connector == null) + { + connector = new MistralChatMessageConnector(); + } + + return agent.RegisterStreamingMiddleware(connector); + } +} diff --git a/dotnet/src/AutoGen.Mistral/Middleware/MistralChatMessageConnector.cs b/dotnet/src/AutoGen.Mistral/Middleware/MistralChatMessageConnector.cs new file mode 100644 index 00000000000..3ba910aa700 --- /dev/null +++ b/dotnet/src/AutoGen.Mistral/Middleware/MistralChatMessageConnector.cs @@ -0,0 +1,319 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// MistralChatMessageConnector.cs + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using AutoGen.Core; + +namespace AutoGen.Mistral; + +public class MistralChatMessageConnector : IStreamingMiddleware, IMiddleware +{ + public string? Name => nameof(MistralChatMessageConnector); + + public async IAsyncEnumerable InvokeAsync(MiddlewareContext context, IStreamingAgent agent, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + var messages = context.Messages; + var chatMessages = ProcessMessage(messages, agent); + var chunks = new List(); + await foreach (var reply in agent.GenerateStreamingReplyAsync(chatMessages, context.Options, cancellationToken)) + { + if (reply is IStreamingMessage chatMessage) + { + chunks.Add(chatMessage.Content); + var response = ProcessChatCompletionResponse(chatMessage, agent); + if (response is not null) + { + yield return response; + } + } + else + { + yield return reply; + } + } + + // if chunks is not empty, then return the aggregate message as the last message + // this is to meet the requirement of streaming call api + // where the last message should be the same result of non-streaming call api + if (chunks.Count == 0) + { + yield break; + } + + var lastResponse = chunks.Last() ?? throw new ArgumentNullException("chunks.Last()"); + var finalResponse = chunks.First() ?? throw new ArgumentNullException("chunks.First()"); + if (lastResponse.Choices!.First().FinishReason == Choice.FinishReasonEnum.ToolCalls) + { + // process as tool call message + foreach (var response in chunks) + { + if (finalResponse.Choices!.First().Message is null) + { + finalResponse.Choices!.First().Message = response.Choices!.First().Delta; + if (finalResponse.Choices!.First().Message!.ToolCalls is null) + { + finalResponse.Choices!.First().Message!.ToolCalls = new List(); + } + } + + if (response.Choices!.First().Delta!.ToolCalls is not null) + { + finalResponse.Choices!.First().Message!.ToolCalls!.AddRange(response.Choices!.First().Delta!.ToolCalls!); + } + + finalResponse.Choices!.First().FinishReason = response.Choices!.First().FinishReason; + + // the usage information will be included in the last message + if (response.Usage is not null) + { + finalResponse.Usage = response.Usage; + } + } + } + else + { + // process as plain text message + foreach (var response in chunks) + { + if (finalResponse.Choices!.First().Message is null) + { + finalResponse.Choices!.First().Message = response.Choices!.First().Delta; + } + + finalResponse.Choices!.First().Message!.Content += response.Choices!.First().Delta!.Content; + finalResponse.Choices!.First().FinishReason = response.Choices!.First().FinishReason; + // the usage information will be included in the last message + if (response.Usage is not null) + { + finalResponse.Usage = response.Usage; + } + } + } + + yield return PostProcessMessage(finalResponse, agent); + } + + public async Task InvokeAsync(MiddlewareContext context, IAgent agent, CancellationToken cancellationToken = default) + { + var messages = context.Messages; + var chatMessages = ProcessMessage(messages, agent); + var response = await agent.GenerateReplyAsync(chatMessages, context.Options, cancellationToken); + + if (response is IMessage chatMessage) + { + return PostProcessMessage(chatMessage.Content, agent); + } + else + { + return response; + } + } + + private IEnumerable ProcessMessage(IEnumerable messages, IAgent agent) + { + return messages.SelectMany(m => + { + if (m is IMessage chatMessage) + { + return [MessageEnvelope.Create(chatMessage.Content, from: chatMessage.From)]; + } + else + { + return m switch + { + TextMessage textMessage => ProcessTextMessage(textMessage, agent), + ToolCallMessage toolCallMessage when (toolCallMessage.From is null || toolCallMessage.From == agent.Name) => ProcessToolCallMessage(toolCallMessage, agent), + ToolCallResultMessage toolCallResultMessage => ProcessToolCallResultMessage(toolCallResultMessage, agent), + AggregateMessage aggregateMessage => ProcessFunctionCallMiddlewareMessage(aggregateMessage, agent), // message type support for functioncall middleware + _ => [m], + }; + } + }); + } + + private IMessage PostProcessMessage(ChatCompletionResponse response, IAgent from) + { + if (response.Choices is null) + { + throw new ArgumentNullException("response.Choices"); + } + + if (response.Choices?.Count != 1) + { + throw new NotSupportedException("response.Choices.Count != 1"); + } + + var choice = response.Choices[0]; + var finishReason = choice.FinishReason ?? throw new ArgumentNullException("choice.FinishReason"); + + if (finishReason == Choice.FinishReasonEnum.Stop || finishReason == Choice.FinishReasonEnum.Length) + { + return new TextMessage(Role.Assistant, choice.Message?.Content ?? throw new ArgumentNullException("choice.Message.Content"), from: from.Name); + } + else if (finishReason == Choice.FinishReasonEnum.ToolCalls) + { + var functionContents = choice.Message?.ToolCalls ?? throw new ArgumentNullException("choice.Message.ToolCalls"); + var toolCalls = functionContents.Select(f => new ToolCall(f.Function.Name, f.Function.Arguments)).ToList(); + return new ToolCallMessage(toolCalls, from: from.Name); + } + else + { + throw new NotSupportedException($"FinishReason {finishReason} is not supported"); + } + } + + private IStreamingMessage? ProcessChatCompletionResponse(IStreamingMessage message, IAgent agent) + { + var response = message.Content; + if (response.VarObject != "chat.completion.chunk") + { + throw new NotSupportedException($"VarObject {response.VarObject} is not supported"); + } + if (response.Choices is null) + { + throw new ArgumentNullException("response.Choices"); + } + + if (response.Choices?.Count != 1) + { + throw new NotSupportedException("response.Choices.Count != 1"); + } + + var choice = response.Choices[0]; + var delta = choice.Delta; + + // process text message if delta.content is not null + if (delta?.Content is string content) + { + return new TextMessageUpdate(role: Role.Assistant, content, from: agent.Name); + } + else if (delta?.ToolCalls is var toolCalls && toolCalls is { Count: 1 }) + { + var toolCall = toolCalls[0]; + var functionContent = toolCall.Function; + + return new ToolCallMessageUpdate(functionContent.Name, functionContent.Arguments, from: agent.Name); + } + else + { + return null; + } + } + + private IEnumerable> ProcessTextMessage(TextMessage textMessage, IAgent agent) + { + IEnumerable messages; + // check if textMessage is system message + if (textMessage.Role == Role.System) + { + messages = [new ChatMessage(ChatMessage.RoleEnum.System, textMessage.Content)]; + } + else if (textMessage.From == agent.Name) + { + // if this message is from agent iteself, then its role should be assistant + messages = [new ChatMessage(ChatMessage.RoleEnum.Assistant, textMessage.Content)]; + } + else if (textMessage.From is null) + { + // if from is null, then process the message based on the role + if (textMessage.Role == Role.User) + { + messages = [new ChatMessage(ChatMessage.RoleEnum.User, textMessage.Content)]; + } + else if (textMessage.Role == Role.Assistant) + { + messages = [new ChatMessage(ChatMessage.RoleEnum.Assistant, textMessage.Content)]; + } + else + { + throw new NotSupportedException($"Role {textMessage.Role} is not supported"); + } + } + else + { + // if from is not null, then the message is from user + messages = [new ChatMessage(ChatMessage.RoleEnum.User, textMessage.Content)]; + } + + return messages.Select(m => new MessageEnvelope(m, from: textMessage.From)); + } + + private IEnumerable> ProcessToolCallResultMessage(ToolCallResultMessage toolCallResultMessage, IAgent agent) + { + var from = toolCallResultMessage.From; + var messages = new List(); + foreach (var toolCall in toolCallResultMessage.ToolCalls) + { + if (toolCall.Result is null) + { + continue; + } + + var message = new ChatMessage(ChatMessage.RoleEnum.Tool, content: toolCall.Result) + { + Name = toolCall.FunctionName, + }; + + messages.Add(message); + } + + return messages.Select(m => new MessageEnvelope(m, from: toolCallResultMessage.From)); + } + + /// + /// Process the aggregate message from function call middleware. If the message is from another agent, this message will be interpreted as an ordinary plain . + /// If the message is from the same agent or the from field is empty, this message will be expanded to the tool call message and tool call result message. + /// + /// + /// + /// + /// + private IEnumerable> ProcessFunctionCallMiddlewareMessage(AggregateMessage aggregateMessage, IAgent agent) + { + if (aggregateMessage.From is string from && from != agent.Name) + { + // if the message is from another agent, then interpret it as a plain text message + // where the content of the plain text message is the content of the tool call result message + var contents = aggregateMessage.Message2.ToolCalls.Select(t => t.Result); + var messages = contents.Select(c => new ChatMessage(ChatMessage.RoleEnum.Assistant, c)); + + return messages.Select(m => new MessageEnvelope(m, from: from)); + } + + // if the message is from the same agent or the from field is empty, then expand the message to tool call message and tool call result message + var toolCallMessage = aggregateMessage.Message1; + var toolCallResultMessage = aggregateMessage.Message2; + + return this.ProcessToolCallMessage(toolCallMessage, agent).Concat(this.ProcessToolCallResultMessage(toolCallResultMessage, agent)); + } + + private IEnumerable> ProcessToolCallMessage(ToolCallMessage toolCallMessage, IAgent agent) + { + IEnumerable messages; + + // the scenario is not support when tool call message is from another agent + if (toolCallMessage.From is string from && from != agent.Name) + { + throw new NotSupportedException("Tool call message from another agent is not supported"); + } + + // convert tool call message to chat message + var chatMessage = new ChatMessage(ChatMessage.RoleEnum.Assistant); + chatMessage.ToolCalls = new List(); + foreach (var toolCall in toolCallMessage.ToolCalls) + { + var functionCall = new FunctionContent.FunctionCall(toolCall.FunctionName, toolCall.FunctionArguments); + var functionContent = new FunctionContent(functionCall); + chatMessage.ToolCalls.Add(functionContent); + } + + messages = [chatMessage]; + + return messages.Select(m => new MessageEnvelope(m, from: toolCallMessage.From)); + } +} diff --git a/dotnet/src/AutoGen.Mistral/MistralAIModelID.cs b/dotnet/src/AutoGen.Mistral/MistralAIModelID.cs new file mode 100644 index 00000000000..a0571281c94 --- /dev/null +++ b/dotnet/src/AutoGen.Mistral/MistralAIModelID.cs @@ -0,0 +1,14 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// MistralAIModelID.cs + +namespace AutoGen.Mistral; + +public class MistralAIModelID +{ + public const string OPEN_MISTRAL_7B = "open-mistral-7b"; + public const string OPEN_MISTRAL_8X7B = "open-mixtral-8x7b"; + public const string OPEN_MISTRAL_8X22B = "open-mixtral-8x22b"; + public const string MISTRAL_SMALL_LATEST = "mistral-small-latest"; + public const string MISTRAL_MEDIUM_LATEST = "mistral-medium-latest"; + public const string MISTRAL_LARGE_LATEST = "mistral-large-latest"; +} diff --git a/dotnet/src/AutoGen.Mistral/MistralClient.cs b/dotnet/src/AutoGen.Mistral/MistralClient.cs new file mode 100644 index 00000000000..5fc3d110985 --- /dev/null +++ b/dotnet/src/AutoGen.Mistral/MistralClient.cs @@ -0,0 +1,168 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// MistralClient.cs + +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.Http; +using System.Security.Authentication; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Threading.Tasks; + +namespace AutoGen.Mistral; + +public class MistralClient : IDisposable +{ + private readonly HttpClient _httpClient; + private readonly string baseUrl = "https://api.mistral.ai/v1"; + + public MistralClient(string apiKey, string? baseUrl = null) + { + _httpClient = new HttpClient(); + _httpClient.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); + _httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {apiKey}"); + this.baseUrl = baseUrl ?? this.baseUrl; + } + + public MistralClient(HttpClient httpClient, string? baseUrl = null) + { + _httpClient = httpClient; + _httpClient.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); + this.baseUrl = baseUrl ?? this.baseUrl; + } + + public async Task CreateChatCompletionsAsync(ChatCompletionRequest chatCompletionRequest) + { + chatCompletionRequest.Stream = false; + var response = await HttpRequestRaw(HttpMethod.Post, chatCompletionRequest); + response.EnsureSuccessStatusCode(); + + var responseStream = await response.Content.ReadAsStreamAsync(); + return await JsonSerializer.DeserializeAsync(responseStream) ?? throw new Exception("Failed to deserialize response"); + } + + public async IAsyncEnumerable StreamingChatCompletionsAsync(ChatCompletionRequest chatCompletionRequest) + { + chatCompletionRequest.Stream = true; + var response = await HttpRequestRaw(HttpMethod.Post, chatCompletionRequest, streaming: true); + using var stream = await response.Content.ReadAsStreamAsync(); + using StreamReader reader = new StreamReader(stream); + string line; + + SseEvent currentEvent = new SseEvent(); + while ((line = await reader.ReadLineAsync()) != null) + { + if (!string.IsNullOrEmpty(line)) + { + currentEvent.Data = line.Substring("data:".Length).Trim(); + } + else // an empty line indicates the end of an event + { + if (currentEvent.Data == "[DONE]") + { + continue; + } + else if (currentEvent.EventType == null) + { + var res = await JsonSerializer.DeserializeAsync( + new MemoryStream(Encoding.UTF8.GetBytes(currentEvent.Data))) ?? throw new Exception("Failed to deserialize response"); + yield return res; + } + else if (currentEvent.EventType != null) + { + var res = await JsonSerializer.DeserializeAsync( + new MemoryStream(Encoding.UTF8.GetBytes(currentEvent.Data))); + throw new Exception(res?.Error.Message); + } + + // Reset the current event for the next one + currentEvent = new SseEvent(); + } + } + } + + protected async Task HttpRequestRaw(HttpMethod verb, object postData, bool streaming = false) + { + var url = $"{baseUrl}/chat/completions"; + HttpResponseMessage response; + string resultAsString; + HttpRequestMessage req = new HttpRequestMessage(verb, url); + + if (postData != null) + { + if (postData is HttpContent) + { + req.Content = postData as HttpContent; + } + else + { + string jsonContent = JsonSerializer.Serialize(postData, + new JsonSerializerOptions() { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }); + var stringContent = new StringContent(jsonContent, Encoding.UTF8, "application/json"); + req.Content = stringContent; + } + } + + response = await this._httpClient.SendAsync(req, + streaming ? HttpCompletionOption.ResponseHeadersRead : HttpCompletionOption.ResponseContentRead); + + if (response.IsSuccessStatusCode) + { + return response; + } + else + { + try + { + resultAsString = await response.Content.ReadAsStringAsync(); + } + catch (Exception e) + { + resultAsString = + "Additionally, the following error was thrown when attempting to read the response content: " + + e.ToString(); + } + + if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized) + { + throw new AuthenticationException( + "Mistral rejected your authorization, most likely due to an invalid API Key. Full API response follows: " + + resultAsString); + } + else if (response.StatusCode == System.Net.HttpStatusCode.InternalServerError) + { + throw new HttpRequestException( + "Mistral had an internal server error, which can happen occasionally. Please retry your request. " + + GetErrorMessage(resultAsString, response, url, url)); + } + else + { + throw new HttpRequestException(GetErrorMessage(resultAsString, response, url, url)); + } + } + } + + private string GetErrorMessage(string resultAsString, HttpResponseMessage response, string name, string description = "") + { + return $"Error at {name} ({description}) with HTTP status code: {response.StatusCode}. Content: {resultAsString ?? ""}"; + } + + public void Dispose() + { + _httpClient.Dispose(); + } + + public class SseEvent + { + public SseEvent(string? eventType = null, string? data = null) + { + EventType = eventType; + Data = data; + } + + public string? EventType { get; set; } + public string? Data { get; set; } + } +} diff --git a/dotnet/src/AutoGen.OpenAI/Agent/GPTAgent.cs b/dotnet/src/AutoGen.OpenAI/Agent/GPTAgent.cs new file mode 100644 index 00000000000..52070788e34 --- /dev/null +++ b/dotnet/src/AutoGen.OpenAI/Agent/GPTAgent.cs @@ -0,0 +1,117 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// GPTAgent.cs + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using AutoGen.OpenAI.Extension; +using Azure.AI.OpenAI; + +namespace AutoGen.OpenAI; + +/// +/// GPT agent that can be used to connect to OpenAI chat models like GPT-3.5, GPT-4, etc. +/// supports the following message types as input: +/// - +/// - +/// - +/// - +/// - +/// - +/// - where T is +/// - where TMessage1 is and TMessage2 is +/// +/// returns the following message types: +/// - +/// - +/// - where TMessage1 is and TMessage2 is +/// +public class GPTAgent : IStreamingAgent +{ + private readonly IDictionary>>? functionMap; + private readonly OpenAIClient openAIClient; + private readonly string? modelName; + private readonly OpenAIChatAgent _innerAgent; + + public GPTAgent( + string name, + string systemMessage, + ILLMConfig config, + float temperature = 0.7f, + int maxTokens = 1024, + int? seed = null, + ChatCompletionsResponseFormat? responseFormat = null, + IEnumerable? functions = null, + IDictionary>>? functionMap = null) + { + openAIClient = config switch + { + AzureOpenAIConfig azureConfig => new OpenAIClient(new Uri(azureConfig.Endpoint), new Azure.AzureKeyCredential(azureConfig.ApiKey)), + OpenAIConfig openAIConfig => new OpenAIClient(openAIConfig.ApiKey), + _ => throw new ArgumentException($"Unsupported config type {config.GetType()}"), + }; + + modelName = config switch + { + AzureOpenAIConfig azureConfig => azureConfig.DeploymentName, + OpenAIConfig openAIConfig => openAIConfig.ModelId, + _ => throw new ArgumentException($"Unsupported config type {config.GetType()}"), + }; + + _innerAgent = new OpenAIChatAgent(openAIClient, name, modelName, systemMessage, temperature, maxTokens, seed, responseFormat, functions); + Name = name; + this.functionMap = functionMap; + } + + public GPTAgent( + string name, + string systemMessage, + OpenAIClient openAIClient, + string modelName, + float temperature = 0.7f, + int maxTokens = 1024, + int? seed = null, + ChatCompletionsResponseFormat? responseFormat = null, + IEnumerable? functions = null, + IDictionary>>? functionMap = null) + { + this.openAIClient = openAIClient; + this.modelName = modelName; + Name = name; + this.functionMap = functionMap; + _innerAgent = new OpenAIChatAgent(openAIClient, name, modelName, systemMessage, temperature, maxTokens, seed, responseFormat, functions); + } + + public string Name { get; } + + public async Task GenerateReplyAsync( + IEnumerable messages, + GenerateReplyOptions? options = null, + CancellationToken cancellationToken = default) + { + var agent = this._innerAgent.RegisterMessageConnector(); + if (this.functionMap is not null) + { + var functionMapMiddleware = new FunctionCallMiddleware(functionMap: this.functionMap); + agent = agent.RegisterStreamingMiddleware(functionMapMiddleware); + } + + return await agent.GenerateReplyAsync(messages, options, cancellationToken); + } + + public IAsyncEnumerable GenerateStreamingReplyAsync( + IEnumerable messages, + GenerateReplyOptions? options = null, + CancellationToken cancellationToken = default) + { + var agent = this._innerAgent.RegisterMessageConnector(); + if (this.functionMap is not null) + { + var functionMapMiddleware = new FunctionCallMiddleware(functionMap: this.functionMap); + agent = agent.RegisterStreamingMiddleware(functionMapMiddleware); + } + + return agent.GenerateStreamingReplyAsync(messages, options, cancellationToken); + } +} diff --git a/dotnet/src/AutoGen.OpenAI/Agent/OpenAIChatAgent.cs b/dotnet/src/AutoGen.OpenAI/Agent/OpenAIChatAgent.cs new file mode 100644 index 00000000000..37a4882f69e --- /dev/null +++ b/dotnet/src/AutoGen.OpenAI/Agent/OpenAIChatAgent.cs @@ -0,0 +1,150 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// OpenAIChatAgent.cs + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using AutoGen.OpenAI.Extension; +using Azure.AI.OpenAI; + +namespace AutoGen.OpenAI; + +/// +/// OpenAI client agent. This agent is a thin wrapper around to provide a simple interface for chat completions. +/// To better work with other agents, it's recommended to use which supports more message types and have a better compatibility with other agents. +/// supports the following message types: +/// +/// +/// where T is : chat request message. +/// +/// +/// returns the following message types: +/// +/// +/// where T is : chat response message. +/// where T is : streaming chat completions update. +/// +/// +/// +public class OpenAIChatAgent : IStreamingAgent +{ + private readonly OpenAIClient openAIClient; + private readonly string modelName; + private readonly float _temperature; + private readonly int _maxTokens = 1024; + private readonly IEnumerable? _functions; + private readonly string _systemMessage; + private readonly ChatCompletionsResponseFormat? _responseFormat; + private readonly int? _seed; + + /// + /// Create a new instance of . + /// + /// openai client + /// agent name + /// model name. e.g. gpt-turbo-3.5 + /// system message + /// temperature + /// max tokens to generated + /// response format, set it to to enable json mode. + /// seed to use, set it to enable deterministic output + /// functions + public OpenAIChatAgent( + OpenAIClient openAIClient, + string name, + string modelName, + string systemMessage = "You are a helpful AI assistant", + float temperature = 0.7f, + int maxTokens = 1024, + int? seed = null, + ChatCompletionsResponseFormat? responseFormat = null, + IEnumerable? functions = null) + { + this.openAIClient = openAIClient; + this.modelName = modelName; + this.Name = name; + _temperature = temperature; + _maxTokens = maxTokens; + _functions = functions; + _systemMessage = systemMessage; + _responseFormat = responseFormat; + _seed = seed; + } + + public string Name { get; } + + public async Task GenerateReplyAsync( + IEnumerable messages, + GenerateReplyOptions? options = null, + CancellationToken cancellationToken = default) + { + var settings = this.CreateChatCompletionsOptions(options, messages); + var reply = await this.openAIClient.GetChatCompletionsAsync(settings, cancellationToken); + + return new MessageEnvelope(reply, from: this.Name); + } + + public async IAsyncEnumerable GenerateStreamingReplyAsync( + IEnumerable messages, + GenerateReplyOptions? options = null, + [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + var settings = this.CreateChatCompletionsOptions(options, messages); + var response = await this.openAIClient.GetChatCompletionsStreamingAsync(settings, cancellationToken); + await foreach (var update in response.WithCancellation(cancellationToken)) + { + if (update.ChoiceIndex > 0) + { + throw new InvalidOperationException("Only one choice is supported in streaming response"); + } + + yield return new MessageEnvelope(update, from: this.Name); + } + } + + private ChatCompletionsOptions CreateChatCompletionsOptions(GenerateReplyOptions? options, IEnumerable messages) + { + var oaiMessages = messages.Select(m => m switch + { + IMessage chatRequestMessage => chatRequestMessage.Content, + _ => throw new ArgumentException("Invalid message type") + }); + + // add system message if there's no system message in messages + if (!oaiMessages.Any(m => m is ChatRequestSystemMessage)) + { + oaiMessages = new[] { new ChatRequestSystemMessage(_systemMessage) }.Concat(oaiMessages); + } + + var settings = new ChatCompletionsOptions(this.modelName, oaiMessages) + { + MaxTokens = options?.MaxToken ?? _maxTokens, + Temperature = options?.Temperature ?? _temperature, + ResponseFormat = _responseFormat, + Seed = _seed, + }; + + var openAIFunctionDefinitions = options?.Functions?.Select(f => f.ToOpenAIFunctionDefinition()); + var functions = openAIFunctionDefinitions ?? _functions; + if (functions is not null && functions.Count() > 0) + { + foreach (var f in functions) + { + settings.Tools.Add(new ChatCompletionsFunctionToolDefinition(f)); + } + } + + if (options?.StopSequence is var sequence && sequence is { Length: > 0 }) + { + foreach (var seq in sequence) + { + settings.StopSequences.Add(seq); + } + } + + return settings; + } +} diff --git a/dotnet/src/AutoGen.OpenAI/AutoGen.OpenAI.csproj b/dotnet/src/AutoGen.OpenAI/AutoGen.OpenAI.csproj new file mode 100644 index 00000000000..7220cfe5c62 --- /dev/null +++ b/dotnet/src/AutoGen.OpenAI/AutoGen.OpenAI.csproj @@ -0,0 +1,25 @@ +ο»Ώ + + netstandard2.0 + AutoGen.OpenAI + + + + + + + AutoGen.OpenAI + + OpenAI Intergration for AutoGen. + + + + + + + + + + + + diff --git a/dotnet/src/AutoGen.OpenAI/AzureOpenAIConfig.cs b/dotnet/src/AutoGen.OpenAI/AzureOpenAIConfig.cs new file mode 100644 index 00000000000..31df784ed21 --- /dev/null +++ b/dotnet/src/AutoGen.OpenAI/AzureOpenAIConfig.cs @@ -0,0 +1,23 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// AzureOpenAIConfig.cs + +namespace AutoGen.OpenAI; + +public class AzureOpenAIConfig : ILLMConfig +{ + public AzureOpenAIConfig(string endpoint, string deploymentName, string apiKey, string? modelId = null) + { + this.Endpoint = endpoint; + this.DeploymentName = deploymentName; + this.ApiKey = apiKey; + this.ModelId = modelId; + } + + public string Endpoint { get; } + + public string DeploymentName { get; } + + public string ApiKey { get; } + + public string? ModelId { get; } +} diff --git a/dotnet/src/AutoGen.OpenAI/Extension/FunctionContractExtension.cs b/dotnet/src/AutoGen.OpenAI/Extension/FunctionContractExtension.cs new file mode 100644 index 00000000000..4accdc4d8d4 --- /dev/null +++ b/dotnet/src/AutoGen.OpenAI/Extension/FunctionContractExtension.cs @@ -0,0 +1,63 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// FunctionContractExtension.cs + +using System; +using System.Collections.Generic; +using Azure.AI.OpenAI; +using Json.Schema; +using Json.Schema.Generation; + +namespace AutoGen.OpenAI.Extension; + +public static class FunctionContractExtension +{ + /// + /// Convert a to a that can be used in gpt funciton call. + /// + /// function contract + /// + public static FunctionDefinition ToOpenAIFunctionDefinition(this FunctionContract functionContract) + { + var functionDefinition = new FunctionDefinition + { + Name = functionContract.Name, + Description = functionContract.Description, + }; + var requiredParameterNames = new List(); + var propertiesSchemas = new Dictionary(); + var propertySchemaBuilder = new JsonSchemaBuilder().Type(SchemaValueType.Object); + foreach (var param in functionContract.Parameters ?? []) + { + if (param.Name is null) + { + throw new InvalidOperationException("Parameter name cannot be null"); + } + + var schemaBuilder = new JsonSchemaBuilder().FromType(param.ParameterType ?? throw new ArgumentNullException(nameof(param.ParameterType))); + if (param.Description != null) + { + schemaBuilder = schemaBuilder.Description(param.Description); + } + + if (param.IsRequired) + { + requiredParameterNames.Add(param.Name); + } + + var schema = schemaBuilder.Build(); + propertiesSchemas[param.Name] = schema; + + } + propertySchemaBuilder = propertySchemaBuilder.Properties(propertiesSchemas); + propertySchemaBuilder = propertySchemaBuilder.Required(requiredParameterNames); + + var option = new System.Text.Json.JsonSerializerOptions() + { + PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase + }; + + functionDefinition.Parameters = BinaryData.FromObjectAsJson(propertySchemaBuilder.Build(), option); + + return functionDefinition; + } +} diff --git a/dotnet/src/AutoGen.OpenAI/Extension/MessageExtension.cs b/dotnet/src/AutoGen.OpenAI/Extension/MessageExtension.cs new file mode 100644 index 00000000000..b3dfb1e8668 --- /dev/null +++ b/dotnet/src/AutoGen.OpenAI/Extension/MessageExtension.cs @@ -0,0 +1,228 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// MessageExtension.cs + +using System; +using System.Collections.Generic; +using System.Linq; +using Azure.AI.OpenAI; + +namespace AutoGen.OpenAI; + +public static class MessageExtension +{ + public static string TEXT_CONTENT_TYPE = "text"; + public static string IMAGE_CONTENT_TYPE = "image"; + public static ChatRequestUserMessage ToChatRequestUserMessage(this Message message) + { + if (message.Value is ChatRequestUserMessage message1) + { + return message1; + } + else if (message?.Metadata is { Count: > 0 }) + { + var itemList = new List(); + foreach (var item in message.Metadata) + { + if (item.Key == TEXT_CONTENT_TYPE && item.Value is string txt) + { + itemList.Add(new ChatMessageTextContentItem(txt)); + } + else if (item.Key == IMAGE_CONTENT_TYPE && item.Value is string url) + { + itemList.Add(new ChatMessageImageContentItem(new Uri(url))); + } + } + + if (itemList.Count > 0) + { + return new ChatRequestUserMessage(itemList); + } + else + { + throw new ArgumentException("Content is null and metadata is null"); + } + } + else if (!string.IsNullOrEmpty(message?.Content)) + { + return new ChatRequestUserMessage(message!.Content); + } + + throw new ArgumentException("Content is null and metadata is null"); + } + + public static IEnumerable ToOpenAIChatRequestMessage(this IAgent agent, IMessage message) + { + if (message is IMessage oaiMessage) + { + // short-circuit + return [oaiMessage.Content]; + } + + if (message.From != agent.Name) + { + if (message is TextMessage textMessage) + { + if (textMessage.Role == Role.System) + { + var msg = new ChatRequestSystemMessage(textMessage.Content); + + return [msg]; + } + else + { + var msg = new ChatRequestUserMessage(textMessage.Content); + return [msg]; + } + } + else if (message is ImageMessage imageMessage) + { + // multi-modal + var msg = new ChatRequestUserMessage(new ChatMessageImageContentItem(new Uri(imageMessage.Url ?? imageMessage.BuildDataUri()))); + + return [msg]; + } + else if (message is ToolCallMessage) + { + throw new ArgumentException($"ToolCallMessage is not supported when message.From is not the same with agent"); + } + else if (message is ToolCallResultMessage toolCallResult) + { + return toolCallResult.ToolCalls.Select(m => + { + var msg = new ChatRequestToolMessage(m.Result, m.FunctionName); + + return msg; + }); + } + else if (message is MultiModalMessage multiModalMessage) + { + var messageContent = multiModalMessage.Content.Select(m => + { + return m switch + { + TextMessage textMessage => new ChatMessageTextContentItem(textMessage.Content), + ImageMessage imageMessage => new ChatMessageImageContentItem(new Uri(imageMessage.Url ?? imageMessage.BuildDataUri())), + _ => throw new ArgumentException($"Unknown message type: {m.GetType()}") + }; + }); + + var msg = new ChatRequestUserMessage(messageContent); + return [msg]; + } + else if (message is AggregateMessage aggregateMessage) + { + // convert as user message + var resultMessage = aggregateMessage.Message2; + return resultMessage.ToolCalls.Select(m => new ChatRequestUserMessage(m.Result)); + } + else if (message is Message msg) + { + if (msg.Role == Role.System) + { + var systemMessage = new ChatRequestSystemMessage(msg.Content ?? string.Empty); + return [systemMessage]; + } + else if (msg.FunctionName is null && msg.FunctionArguments is null) + { + var userMessage = msg.ToChatRequestUserMessage(); + return [userMessage]; + } + else if (msg.FunctionName is not null && msg.FunctionArguments is not null && msg.Content is not null) + { + if (msg.Role == Role.Function) + { + return [new ChatRequestFunctionMessage(msg.FunctionName, msg.Content)]; + } + else + { + return [new ChatRequestUserMessage(msg.Content)]; + } + } + else + { + var userMessage = new ChatRequestUserMessage(msg.Content ?? throw new ArgumentException("Content is null")); + return [userMessage]; + } + } + else + { + throw new ArgumentException($"Unknown message type: {message.GetType()}"); + } + } + else + { + if (message is TextMessage textMessage) + { + if (textMessage.Role == Role.System) + { + throw new ArgumentException("System message is not supported when message.From is the same with agent"); + } + + + return [new ChatRequestAssistantMessage(textMessage.Content)]; + } + else if (message is ToolCallMessage toolCallMessage) + { + var assistantMessage = new ChatRequestAssistantMessage(string.Empty); + var toolCalls = toolCallMessage.ToolCalls.Select(tc => new ChatCompletionsFunctionToolCall(tc.FunctionName, tc.FunctionName, tc.FunctionArguments)); + foreach (var tc in toolCalls) + { + assistantMessage.ToolCalls.Add(tc); + } + + return [assistantMessage]; + } + else if (message is AggregateMessage aggregateMessage) + { + var toolCallMessage1 = aggregateMessage.Message1; + var toolCallResultMessage = aggregateMessage.Message2; + + var assistantMessage = new ChatRequestAssistantMessage(string.Empty); + var toolCalls = toolCallMessage1.ToolCalls.Select(tc => new ChatCompletionsFunctionToolCall(tc.FunctionName, tc.FunctionName, tc.FunctionArguments)); + foreach (var tc in toolCalls) + { + assistantMessage.ToolCalls.Add(tc); + } + + var toolCallResults = toolCallResultMessage.ToolCalls.Select(tc => new ChatRequestToolMessage(tc.Result, tc.FunctionName)); + + // return assistantMessage and tool call result messages + var messages = new List { assistantMessage }; + messages.AddRange(toolCallResults); + + return messages; + } + else if (message is Message msg) + { + if (msg.FunctionArguments is not null && msg.FunctionName is not null && msg.Content is not null) + { + var assistantMessage = new ChatRequestAssistantMessage(msg.Content); + assistantMessage.FunctionCall = new FunctionCall(msg.FunctionName, msg.FunctionArguments); + var functionCallMessage = new ChatRequestFunctionMessage(msg.FunctionName, msg.Content); + return [assistantMessage, functionCallMessage]; + } + else + { + if (msg.Role == Role.Function) + { + return [new ChatRequestFunctionMessage(msg.FunctionName!, msg.Content!)]; + } + else + { + var assistantMessage = new ChatRequestAssistantMessage(msg.Content!); + if (msg.FunctionName is not null && msg.FunctionArguments is not null) + { + assistantMessage.FunctionCall = new FunctionCall(msg.FunctionName, msg.FunctionArguments); + } + + return [assistantMessage]; + } + } + } + else + { + throw new ArgumentException($"Unknown message type: {message.GetType()}"); + } + } + } +} diff --git a/dotnet/src/AutoGen.OpenAI/Extension/OpenAIAgentExtension.cs b/dotnet/src/AutoGen.OpenAI/Extension/OpenAIAgentExtension.cs new file mode 100644 index 00000000000..1e8ae58954e --- /dev/null +++ b/dotnet/src/AutoGen.OpenAI/Extension/OpenAIAgentExtension.cs @@ -0,0 +1,37 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// OpenAIAgentExtension.cs + +namespace AutoGen.OpenAI.Extension; + +public static class OpenAIAgentExtension +{ + /// + /// Register an to the + /// + /// the connector to use. If null, a new instance of will be created. + public static MiddlewareStreamingAgent RegisterMessageConnector( + this OpenAIChatAgent agent, OpenAIChatRequestMessageConnector? connector = null) + { + if (connector == null) + { + connector = new OpenAIChatRequestMessageConnector(); + } + + return agent.RegisterStreamingMiddleware(connector); + } + + /// + /// Register an to the where T is + /// + /// the connector to use. If null, a new instance of will be created. + public static MiddlewareStreamingAgent RegisterMessageConnector( + this MiddlewareStreamingAgent agent, OpenAIChatRequestMessageConnector? connector = null) + { + if (connector == null) + { + connector = new OpenAIChatRequestMessageConnector(); + } + + return agent.RegisterStreamingMiddleware(connector); + } +} diff --git a/dotnet/src/AutoGen.OpenAI/GlobalUsing.cs b/dotnet/src/AutoGen.OpenAI/GlobalUsing.cs new file mode 100644 index 00000000000..d66bf001ed5 --- /dev/null +++ b/dotnet/src/AutoGen.OpenAI/GlobalUsing.cs @@ -0,0 +1,4 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// GlobalUsing.cs + +global using AutoGen.Core; diff --git a/dotnet/src/AutoGen.OpenAI/Middleware/OpenAIChatRequestMessageConnector.cs b/dotnet/src/AutoGen.OpenAI/Middleware/OpenAIChatRequestMessageConnector.cs new file mode 100644 index 00000000000..2925a43e16f --- /dev/null +++ b/dotnet/src/AutoGen.OpenAI/Middleware/OpenAIChatRequestMessageConnector.cs @@ -0,0 +1,374 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// OpenAIChatRequestMessageConnector.cs + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using Azure.AI.OpenAI; + +namespace AutoGen.OpenAI; + +/// +/// This middleware converts the incoming to where T is before sending to agent. And converts the output to after receiving from agent. +/// Supported are +/// - +/// - +/// - +/// - +/// - +/// - where T is +/// - where TMessage1 is and TMessage2 is +/// +public class OpenAIChatRequestMessageConnector : IMiddleware, IStreamingMiddleware +{ + private bool strictMode = false; + + /// + /// Create a new instance of . + /// + /// If true, will throw an + /// When the message type is not supported. If false, it will ignore the unsupported message type. + public OpenAIChatRequestMessageConnector(bool strictMode = false) + { + this.strictMode = strictMode; + } + + public string? Name => nameof(OpenAIChatRequestMessageConnector); + + public async Task InvokeAsync(MiddlewareContext context, IAgent agent, CancellationToken cancellationToken = default) + { + var chatMessages = ProcessIncomingMessages(agent, context.Messages); + + var reply = await agent.GenerateReplyAsync(chatMessages, context.Options, cancellationToken); + + return PostProcessMessage(reply); + } + + public async IAsyncEnumerable InvokeAsync( + MiddlewareContext context, + IStreamingAgent agent, + [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + var chatMessages = ProcessIncomingMessages(agent, context.Messages); + var streamingReply = agent.GenerateStreamingReplyAsync(chatMessages, context.Options, cancellationToken); + string? currentToolName = null; + await foreach (var reply in streamingReply) + { + if (reply is IStreamingMessage update) + { + if (update.Content.FunctionName is string functionName) + { + currentToolName = functionName; + } + else if (update.Content.ToolCallUpdate is StreamingFunctionToolCallUpdate toolCallUpdate && toolCallUpdate.Name is string toolCallName) + { + currentToolName = toolCallName; + } + var postProcessMessage = PostProcessStreamingMessage(update, currentToolName); + if (postProcessMessage != null) + { + yield return postProcessMessage; + } + } + else + { + if (this.strictMode) + { + throw new InvalidOperationException($"Invalid streaming message type {reply.GetType().Name}"); + } + else + { + yield return reply; + } + } + } + } + + public IMessage PostProcessMessage(IMessage message) + { + return message switch + { + IMessage m => PostProcessChatResponseMessage(m.Content, m.From), + IMessage m => PostProcessChatCompletions(m), + _ when strictMode is false => message, + _ => throw new InvalidOperationException($"Invalid return message type {message.GetType().Name}"), + }; + } + + public IStreamingMessage? PostProcessStreamingMessage(IStreamingMessage update, string? currentToolName) + { + if (update.Content.ContentUpdate is string contentUpdate) + { + // text message + return new TextMessageUpdate(Role.Assistant, contentUpdate, from: update.From); + } + else if (update.Content.FunctionName is string functionName) + { + return new ToolCallMessageUpdate(functionName, string.Empty, from: update.From); + } + else if (update.Content.FunctionArgumentsUpdate is string functionArgumentsUpdate && currentToolName is string) + { + return new ToolCallMessageUpdate(currentToolName, functionArgumentsUpdate, from: update.From); + } + else if (update.Content.ToolCallUpdate is StreamingFunctionToolCallUpdate tooCallUpdate && currentToolName is string) + { + return new ToolCallMessageUpdate(tooCallUpdate.Name ?? currentToolName, tooCallUpdate.ArgumentsUpdate, from: update.From); + } + else + { + return null; + } + } + + private IMessage PostProcessChatCompletions(IMessage message) + { + // throw exception if prompt filter results is not null + if (message.Content.Choices[0].FinishReason == CompletionsFinishReason.ContentFiltered) + { + throw new InvalidOperationException("The content is filtered because its potential risk. Please try another input."); + } + + return PostProcessChatResponseMessage(message.Content.Choices[0].Message, message.From); + } + + private IMessage PostProcessChatResponseMessage(ChatResponseMessage chatResponseMessage, string? from) + { + if (chatResponseMessage.Content is string content && !string.IsNullOrEmpty(content)) + { + return new TextMessage(Role.Assistant, content, from); + } + + if (chatResponseMessage.FunctionCall is FunctionCall functionCall) + { + return new ToolCallMessage(functionCall.Name, functionCall.Arguments, from); + } + + if (chatResponseMessage.ToolCalls.Where(tc => tc is ChatCompletionsFunctionToolCall).Any()) + { + var functionToolCalls = chatResponseMessage.ToolCalls + .Where(tc => tc is ChatCompletionsFunctionToolCall) + .Select(tc => (ChatCompletionsFunctionToolCall)tc); + + var toolCalls = functionToolCalls.Select(tc => new ToolCall(tc.Name, tc.Arguments)); + + return new ToolCallMessage(toolCalls, from); + } + + throw new InvalidOperationException("Invalid ChatResponseMessage"); + } + + public IEnumerable ProcessIncomingMessages(IAgent agent, IEnumerable messages) + { + return messages.SelectMany(m => + { + if (m is IMessage crm) + { + return [crm]; + } + else + { + var chatRequestMessages = m switch + { + TextMessage textMessage => ProcessTextMessage(agent, textMessage), + ImageMessage imageMessage when (imageMessage.From is null || imageMessage.From != agent.Name) => ProcessImageMessage(agent, imageMessage), + MultiModalMessage multiModalMessage when (multiModalMessage.From is null || multiModalMessage.From != agent.Name) => ProcessMultiModalMessage(agent, multiModalMessage), + ToolCallMessage toolCallMessage when (toolCallMessage.From is null || toolCallMessage.From == agent.Name) => ProcessToolCallMessage(agent, toolCallMessage), + ToolCallResultMessage toolCallResultMessage => ProcessToolCallResultMessage(toolCallResultMessage), + AggregateMessage aggregateMessage => ProcessFunctionCallMiddlewareMessage(agent, aggregateMessage), + Message msg => ProcessMessage(agent, msg), + _ when strictMode is false => [], + _ => throw new InvalidOperationException($"Invalid message type: {m.GetType().Name}"), + }; + + if (chatRequestMessages.Any()) + { + return chatRequestMessages.Select(cm => MessageEnvelope.Create(cm, m.From)); + } + else + { + return [m]; + } + } + }); + } + + private IEnumerable ProcessIncomingMessagesForSelf(Message message) + { + if (message.Role == Role.System) + { + return new[] { new ChatRequestSystemMessage(message.Content) }; + } + else if (message.Content is string content && content is { Length: > 0 }) + { + if (message.FunctionName is null) + { + return new[] { new ChatRequestAssistantMessage(message.Content) }; + } + else + { + return new[] { new ChatRequestToolMessage(content, message.FunctionName) }; + } + } + else if (message.FunctionName is string functionName) + { + var msg = new ChatRequestAssistantMessage(content: null) + { + FunctionCall = new FunctionCall(functionName, message.FunctionArguments) + }; + + return new[] + { + msg, + }; + } + else + { + throw new InvalidOperationException("Invalid Message as message from self."); + } + } + + private IEnumerable ProcessIncomingMessagesForOther(Message message) + { + if (message.Role == Role.System) + { + return [new ChatRequestSystemMessage(message.Content) { Name = message.From }]; + } + else if (message.Content is string content && content is { Length: > 0 }) + { + if (message.FunctionName is not null) + { + return new[] { new ChatRequestToolMessage(content, message.FunctionName) }; + } + + return [new ChatRequestUserMessage(message.Content) { Name = message.From }]; + } + else if (message.FunctionName is string _) + { + return [new ChatRequestUserMessage("// Message type is not supported") { Name = message.From }]; + } + else + { + throw new InvalidOperationException("Invalid Message as message from other."); + } + } + + private IEnumerable ProcessTextMessage(IAgent agent, TextMessage message) + { + if (message.Role == Role.System) + { + return [new ChatRequestSystemMessage(message.Content) { Name = message.From }]; + } + + if (agent.Name == message.From) + { + return [new ChatRequestAssistantMessage(message.Content) { Name = agent.Name }]; + } + else + { + return message.From switch + { + null when message.Role == Role.User => [new ChatRequestUserMessage(message.Content)], + null when message.Role == Role.Assistant => [new ChatRequestAssistantMessage(message.Content)], + null => throw new InvalidOperationException("Invalid Role"), + _ => [new ChatRequestUserMessage(message.Content) { Name = message.From }] + }; + } + } + + private IEnumerable ProcessImageMessage(IAgent agent, ImageMessage message) + { + if (agent.Name == message.From) + { + // image message from assistant is not supported + throw new ArgumentException("ImageMessage is not supported when message.From is the same with agent"); + } + + var imageContentItem = this.CreateChatMessageImageContentItemFromImageMessage(message); + return [new ChatRequestUserMessage([imageContentItem]) { Name = message.From }]; + } + + private IEnumerable ProcessMultiModalMessage(IAgent agent, MultiModalMessage message) + { + if (agent.Name == message.From) + { + // image message from assistant is not supported + throw new ArgumentException("MultiModalMessage is not supported when message.From is the same with agent"); + } + + IEnumerable items = message.Content.Select(ci => ci switch + { + TextMessage text => new ChatMessageTextContentItem(text.Content), + ImageMessage image => this.CreateChatMessageImageContentItemFromImageMessage(image), + _ => throw new NotImplementedException(), + }); + + return [new ChatRequestUserMessage(items) { Name = message.From }]; + } + + private ChatMessageImageContentItem CreateChatMessageImageContentItemFromImageMessage(ImageMessage message) + { + return message.Data is null + ? new ChatMessageImageContentItem(new Uri(message.Url)) + : new ChatMessageImageContentItem(message.Data, message.Data.MediaType); + } + + private IEnumerable ProcessToolCallMessage(IAgent agent, ToolCallMessage message) + { + if (message.From is not null && message.From != agent.Name) + { + throw new ArgumentException("ToolCallMessage is not supported when message.From is not the same with agent"); + } + + var toolCall = message.ToolCalls.Select(tc => new ChatCompletionsFunctionToolCall(tc.FunctionName, tc.FunctionName, tc.FunctionArguments)); + var chatRequestMessage = new ChatRequestAssistantMessage(string.Empty) { Name = message.From }; + foreach (var tc in toolCall) + { + chatRequestMessage.ToolCalls.Add(tc); + } + + return [chatRequestMessage]; + } + + private IEnumerable ProcessToolCallResultMessage(ToolCallResultMessage message) + { + return message.ToolCalls + .Where(tc => tc.Result is not null) + .Select(tc => new ChatRequestToolMessage(tc.Result, tc.FunctionName)); + } + + private IEnumerable ProcessMessage(IAgent agent, Message message) + { + if (message.From is not null && message.From != agent.Name) + { + return ProcessIncomingMessagesForOther(message); + } + else + { + return ProcessIncomingMessagesForSelf(message); + } + } + + private IEnumerable ProcessFunctionCallMiddlewareMessage(IAgent agent, AggregateMessage aggregateMessage) + { + if (aggregateMessage.From is not null && aggregateMessage.From != agent.Name) + { + // convert as user message + var resultMessage = aggregateMessage.Message2; + + return resultMessage.ToolCalls.Select(tc => new ChatRequestUserMessage(tc.Result) { Name = aggregateMessage.From }); + } + else + { + var toolCallMessage1 = aggregateMessage.Message1; + var toolCallResultMessage = aggregateMessage.Message2; + + var assistantMessage = this.ProcessToolCallMessage(agent, toolCallMessage1); + var toolCallResults = this.ProcessToolCallResultMessage(toolCallResultMessage); + + return assistantMessage.Concat(toolCallResults); + } + } +} diff --git a/dotnet/src/AutoGen.OpenAI/OpenAIConfig.cs b/dotnet/src/AutoGen.OpenAI/OpenAIConfig.cs new file mode 100644 index 00000000000..35ce1e491aa --- /dev/null +++ b/dotnet/src/AutoGen.OpenAI/OpenAIConfig.cs @@ -0,0 +1,17 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// OpenAIConfig.cs + +namespace AutoGen.OpenAI; + +public class OpenAIConfig : ILLMConfig +{ + public OpenAIConfig(string apiKey, string modelId) + { + this.ApiKey = apiKey; + this.ModelId = modelId; + } + + public string ApiKey { get; } + + public string ModelId { get; } +} diff --git a/dotnet/src/AutoGen.SemanticKernel/AutoGen.SemanticKernel.csproj b/dotnet/src/AutoGen.SemanticKernel/AutoGen.SemanticKernel.csproj new file mode 100644 index 00000000000..3bd96f93b68 --- /dev/null +++ b/dotnet/src/AutoGen.SemanticKernel/AutoGen.SemanticKernel.csproj @@ -0,0 +1,29 @@ +ο»Ώ + + + netstandard2.0 + AutoGen.SemanticKernel + $(NoWarn);SKEXP0110 + + + + + + + AutoGen.SemanticKernel + + This package contains the semantic kernel integration for AutoGen + + + + + + + + + + + + + + diff --git a/dotnet/src/AutoGen.SemanticKernel/Extension/KernelExtension.cs b/dotnet/src/AutoGen.SemanticKernel/Extension/KernelExtension.cs new file mode 100644 index 00000000000..8eb11934da3 --- /dev/null +++ b/dotnet/src/AutoGen.SemanticKernel/Extension/KernelExtension.cs @@ -0,0 +1,48 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// KernelExtension.cs + +using System.Linq; +using Microsoft.SemanticKernel; + +namespace AutoGen.SemanticKernel.Extension; + +public static class KernelExtension +{ + public static SemanticKernelAgent ToSemanticKernelAgent(this Kernel kernel, string name, string systemMessage = "You are a helpful AI assistant", PromptExecutionSettings? settings = null) + { + return new SemanticKernelAgent(kernel, name, systemMessage, settings); + } + + /// + /// Convert a to a + /// + /// kernel function metadata + public static FunctionContract ToFunctionContract(this KernelFunctionMetadata metadata) + { + return new FunctionContract() + { + Name = metadata.Name, + Description = metadata.Description, + Parameters = metadata.Parameters.Select(p => p.ToFunctionParameterContract()).ToList(), + ReturnType = metadata.ReturnParameter.ParameterType, + ReturnDescription = metadata.ReturnParameter.Description, + ClassName = metadata.PluginName, + }; + } + + /// + /// Convert a to a + /// + /// kernel parameter metadata + public static FunctionParameterContract ToFunctionParameterContract(this KernelParameterMetadata metadata) + { + return new FunctionParameterContract() + { + Name = metadata.Name, + Description = metadata.Description, + DefaultValue = metadata.DefaultValue, + IsRequired = metadata.IsRequired, + ParameterType = metadata.ParameterType, + }; + } +} diff --git a/dotnet/src/AutoGen.SemanticKernel/Extension/SemanticKernelAgentExtension.cs b/dotnet/src/AutoGen.SemanticKernel/Extension/SemanticKernelAgentExtension.cs new file mode 100644 index 00000000000..4d450945dab --- /dev/null +++ b/dotnet/src/AutoGen.SemanticKernel/Extension/SemanticKernelAgentExtension.cs @@ -0,0 +1,37 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// SemanticKernelAgentExtension.cs + +namespace AutoGen.SemanticKernel.Extension; + +public static class SemanticKernelAgentExtension +{ + /// + /// Register an to the + /// + /// the connector to use. If null, a new instance of will be created. + public static MiddlewareStreamingAgent RegisterMessageConnector( + this SemanticKernelAgent agent, SemanticKernelChatMessageContentConnector? connector = null) + { + if (connector == null) + { + connector = new SemanticKernelChatMessageContentConnector(); + } + + return agent.RegisterStreamingMiddleware(connector); + } + + /// + /// Register an to the where T is + /// + /// the connector to use. If null, a new instance of will be created. + public static MiddlewareStreamingAgent RegisterMessageConnector( + this MiddlewareStreamingAgent agent, SemanticKernelChatMessageContentConnector? connector = null) + { + if (connector == null) + { + connector = new SemanticKernelChatMessageContentConnector(); + } + + return agent.RegisterStreamingMiddleware(connector); + } +} diff --git a/dotnet/src/AutoGen.SemanticKernel/GlobalUsing.cs b/dotnet/src/AutoGen.SemanticKernel/GlobalUsing.cs new file mode 100644 index 00000000000..d66bf001ed5 --- /dev/null +++ b/dotnet/src/AutoGen.SemanticKernel/GlobalUsing.cs @@ -0,0 +1,4 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// GlobalUsing.cs + +global using AutoGen.Core; diff --git a/dotnet/src/AutoGen.SemanticKernel/Middleware/KernelPluginMiddleware.cs b/dotnet/src/AutoGen.SemanticKernel/Middleware/KernelPluginMiddleware.cs new file mode 100644 index 00000000000..628915a0302 --- /dev/null +++ b/dotnet/src/AutoGen.SemanticKernel/Middleware/KernelPluginMiddleware.cs @@ -0,0 +1,77 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// KernelPluginMiddleware.cs + +using System; +using System.Linq; +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Threading; +using System.Threading.Tasks; +using AutoGen.SemanticKernel.Extension; +using Microsoft.SemanticKernel; + +namespace AutoGen.SemanticKernel; + +/// +/// A middleware that consumes +/// +public class KernelPluginMiddleware : IMiddleware +{ + private readonly KernelPlugin _kernelPlugin; + private readonly FunctionCallMiddleware _functionCallMiddleware; + public string? Name => nameof(KernelPluginMiddleware); + + public KernelPluginMiddleware(Kernel kernel, KernelPlugin kernelPlugin) + { + _kernelPlugin = kernelPlugin; + var functionContracts = kernelPlugin.Select(k => k.Metadata.ToFunctionContract()); + var functionMap = kernelPlugin.ToDictionary(kv => kv.Metadata.Name, kv => InvokeFunctionPartial(kernel, kv)); + _functionCallMiddleware = new FunctionCallMiddleware(functionContracts, functionMap, Name); + } + + public Task InvokeAsync(MiddlewareContext context, IAgent agent, CancellationToken cancellationToken = default) + { + return _functionCallMiddleware.InvokeAsync(context, agent, cancellationToken); + } + + private async Task InvokeFunctionAsync(Kernel kernel, KernelFunction function, string arguments) + { + var kernelArguments = new KernelArguments(); + var parameters = function.Metadata.Parameters; + var jsonObject = JsonSerializer.Deserialize(arguments) ?? new JsonObject(); + foreach (var parameter in parameters) + { + var parameterName = parameter.Name; + if (jsonObject.ContainsKey(parameterName)) + { + var parameterType = parameter.ParameterType ?? throw new ArgumentException($"Missing parameter type for {parameterName}"); + var parameterValue = jsonObject[parameterName]; + var parameterObject = parameterValue.Deserialize(parameterType); + kernelArguments.Add(parameterName, parameterObject); + } + else + { + if (parameter.DefaultValue != null) + { + kernelArguments.Add(parameterName, parameter.DefaultValue); + } + else if (parameter.IsRequired) + { + throw new ArgumentException($"Missing required parameter: {parameterName}"); + } + } + } + var result = await function.InvokeAsync(kernel, kernelArguments); + + return result.ToString(); + } + + private Func> InvokeFunctionPartial(Kernel kernel, KernelFunction function) + { + return async (string args) => + { + var result = await InvokeFunctionAsync(kernel, function, args); + return result.ToString(); + }; + } +} diff --git a/dotnet/src/AutoGen.SemanticKernel/Middleware/SemanticKernelChatMessageContentConnector.cs b/dotnet/src/AutoGen.SemanticKernel/Middleware/SemanticKernelChatMessageContentConnector.cs new file mode 100644 index 00000000000..6a8395ef22e --- /dev/null +++ b/dotnet/src/AutoGen.SemanticKernel/Middleware/SemanticKernelChatMessageContentConnector.cs @@ -0,0 +1,251 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// SemanticKernelChatMessageContentConnector.cs + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.ChatCompletion; + +namespace AutoGen.SemanticKernel; + +/// +/// This middleware converts the incoming to before passing to agent. +/// And converts the reply message from to before returning to the caller. +/// +/// requirement for agent +/// - Input message type: where T is +/// - Reply message type: where T is +/// - (streaming) Reply message type: where T is +/// +/// This middleware supports the following message types: +/// - +/// - +/// - +/// +/// This middleware returns the following message types: +/// - +/// - +/// - +/// - (streaming) +/// +public class SemanticKernelChatMessageContentConnector : IMiddleware, IStreamingMiddleware +{ + public string? Name => nameof(SemanticKernelChatMessageContentConnector); + + public async Task InvokeAsync(MiddlewareContext context, IAgent agent, CancellationToken cancellationToken = default) + { + var messages = context.Messages; + + var chatMessageContents = ProcessMessage(messages, agent) + .Select(m => new MessageEnvelope(m)); + var reply = await agent.GenerateReplyAsync(chatMessageContents, context.Options, cancellationToken); + + return PostProcessMessage(reply); + } + + public async IAsyncEnumerable InvokeAsync(MiddlewareContext context, IStreamingAgent agent, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + var chatMessageContents = ProcessMessage(context.Messages, agent) + .Select(m => new MessageEnvelope(m)); + + await foreach (var reply in agent.GenerateStreamingReplyAsync(chatMessageContents, context.Options, cancellationToken)) + { + yield return PostProcessStreamingMessage(reply); + } + } + + private IMessage PostProcessMessage(IMessage input) + { + return input switch + { + IMessage messageEnvelope => PostProcessMessage(messageEnvelope), + _ => input, + }; + } + + private IStreamingMessage PostProcessStreamingMessage(IStreamingMessage input) + { + return input switch + { + IStreamingMessage streamingMessage => PostProcessMessage(streamingMessage), + IMessage msg => PostProcessMessage(msg), + _ => input, + }; + } + + private IMessage PostProcessMessage(IMessage messageEnvelope) + { + var chatMessageContent = messageEnvelope.Content; + var items = chatMessageContent.Items.Select(i => i switch + { + TextContent txt => new TextMessage(Role.Assistant, txt.Text!, messageEnvelope.From), + ImageContent img when img.Uri is Uri uri => new ImageMessage(Role.Assistant, uri.ToString(), from: messageEnvelope.From), + ImageContent img when img.Data is ReadOnlyMemory data => new ImageMessage(Role.Assistant, BinaryData.FromBytes(data), from: messageEnvelope.From), + _ => throw new InvalidOperationException("Unsupported content type"), + }); + + if (items.Count() == 1) + { + return items.First(); + } + else + { + return new MultiModalMessage(Role.Assistant, items, from: messageEnvelope.From); + } + } + + private IStreamingMessage PostProcessMessage(IStreamingMessage streamingMessage) + { + var chatMessageContent = streamingMessage.Content; + if (chatMessageContent.ChoiceIndex > 0) + { + throw new InvalidOperationException("Only one choice is supported in streaming response"); + } + return new TextMessageUpdate(Role.Assistant, chatMessageContent.Content, streamingMessage.From); + } + + private IEnumerable ProcessMessage(IEnumerable messages, IAgent agent) + { + return messages.SelectMany(m => + { + if (m is IMessage chatMessageContent) + { + return [chatMessageContent.Content]; + } + if (m.From == agent.Name) + { + return ProcessMessageForSelf(m); + } + else + { + return ProcessMessageForOthers(m); + } + }); + } + + private IEnumerable ProcessMessageForSelf(IMessage message) + { + return message switch + { + TextMessage textMessage => ProcessMessageForSelf(textMessage), + MultiModalMessage multiModalMessage => ProcessMessageForSelf(multiModalMessage), + Message m => ProcessMessageForSelf(m), + _ => throw new System.NotImplementedException(), + }; + } + + private IEnumerable ProcessMessageForOthers(IMessage message) + { + return message switch + { + TextMessage textMessage => ProcessMessageForOthers(textMessage), + MultiModalMessage multiModalMessage => ProcessMessageForOthers(multiModalMessage), + ImageMessage imageMessage => ProcessMessageForOthers(imageMessage), + Message m => ProcessMessageForOthers(m), + _ => throw new InvalidOperationException("unsupported message type, only support TextMessage, ImageMessage, MultiModalMessage and Message."), + }; + } + + private IEnumerable ProcessMessageForSelf(TextMessage message) + { + if (message.Role == Role.System) + { + return [new ChatMessageContent(AuthorRole.System, message.Content)]; + } + else + { + return [new ChatMessageContent(AuthorRole.Assistant, message.Content)]; + } + } + + + private IEnumerable ProcessMessageForOthers(TextMessage message) + { + if (message.Role == Role.System) + { + return [new ChatMessageContent(AuthorRole.System, message.Content)]; + } + else + { + return [new ChatMessageContent(AuthorRole.User, message.Content)]; + } + } + + private IEnumerable ProcessMessageForOthers(ImageMessage message) + { + var collectionItems = new ChatMessageContentItemCollection(); + collectionItems.Add(new ImageContent(new Uri(message.Url ?? message.BuildDataUri()))); + return [new ChatMessageContent(AuthorRole.User, collectionItems)]; + } + + private IEnumerable ProcessMessageForSelf(MultiModalMessage message) + { + throw new System.InvalidOperationException("MultiModalMessage is not supported in the semantic kernel if it's from self."); + } + + private IEnumerable ProcessMessageForOthers(MultiModalMessage message) + { + var collections = new ChatMessageContentItemCollection(); + foreach (var item in message.Content) + { + if (item is TextMessage textContent) + { + collections.Add(new TextContent(textContent.Content)); + } + else if (item is ImageMessage imageContent) + { + collections.Add(new ImageContent(new Uri(imageContent.Url ?? imageContent.BuildDataUri()))); + } + else + { + throw new InvalidOperationException($"Unsupported message type: {item.GetType().Name}"); + } + } + return [new ChatMessageContent(AuthorRole.User, collections)]; + } + + + private IEnumerable ProcessMessageForSelf(Message message) + { + if (message.Role == Role.System) + { + return [new ChatMessageContent(AuthorRole.System, message.Content)]; + } + else if (message.Content is string && message.FunctionName is null && message.FunctionArguments is null) + { + return [new ChatMessageContent(AuthorRole.Assistant, message.Content)]; + } + else if (message.Content is null && message.FunctionName is not null && message.FunctionArguments is not null) + { + throw new System.InvalidOperationException("Function call is not supported in the semantic kernel if it's from self."); + } + else + { + throw new System.InvalidOperationException("Unsupported message type"); + } + } + + private IEnumerable ProcessMessageForOthers(Message message) + { + if (message.Role == Role.System) + { + return [new ChatMessageContent(AuthorRole.System, message.Content)]; + } + else if (message.Content is string && message.FunctionName is null && message.FunctionArguments is null) + { + return [new ChatMessageContent(AuthorRole.User, message.Content)]; + } + else if (message.Content is null && message.FunctionName is not null && message.FunctionArguments is not null) + { + throw new System.InvalidOperationException("Function call is not supported in the semantic kernel if it's from others."); + } + else + { + throw new System.InvalidOperationException("Unsupported message type"); + } + } +} diff --git a/dotnet/src/AutoGen.SemanticKernel/SemanticKernelAgent.cs b/dotnet/src/AutoGen.SemanticKernel/SemanticKernelAgent.cs new file mode 100644 index 00000000000..21f652f56c4 --- /dev/null +++ b/dotnet/src/AutoGen.SemanticKernel/SemanticKernelAgent.cs @@ -0,0 +1,121 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// SemanticKernelAgent.cs + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.ChatCompletion; +using Microsoft.SemanticKernel.Connectors.OpenAI; + +namespace AutoGen.SemanticKernel; + +/// +/// Semantic Kernel Agent +/// Income message could be one of the following type: +/// +/// where T is +/// +/// +/// Return message could be one of the following type: +/// +/// where T is +/// (streaming) where T is +/// +/// +/// To support more AutoGen built-in , register with . +/// +public class SemanticKernelAgent : IStreamingAgent +{ + private readonly Kernel _kernel; + private readonly string _systemMessage; + private readonly PromptExecutionSettings? _settings; + + public SemanticKernelAgent( + Kernel kernel, + string name, + string systemMessage = "You are a helpful AI assistant", + PromptExecutionSettings? settings = null) + { + _kernel = kernel; + this.Name = name; + _systemMessage = systemMessage; + _settings = settings; + } + + public string Name { get; } + + + public async Task GenerateReplyAsync(IEnumerable messages, GenerateReplyOptions? options = null, CancellationToken cancellationToken = default) + { + var chatHistory = BuildChatHistory(messages); + var option = BuildOption(options); + var chatService = _kernel.GetRequiredService(); + + var reply = await chatService.GetChatMessageContentsAsync(chatHistory, option, _kernel, cancellationToken); + + if (reply.Count > 1) + { + throw new InvalidOperationException("ResultsPerPrompt greater than 1 is not supported in this semantic kernel agent"); + } + + return new MessageEnvelope(reply.First(), from: this.Name); + } + + public async IAsyncEnumerable GenerateStreamingReplyAsync( + IEnumerable messages, + GenerateReplyOptions? options = null, + [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + var chatHistory = BuildChatHistory(messages); + var option = BuildOption(options); + var chatService = _kernel.GetRequiredService(); + var response = chatService.GetStreamingChatMessageContentsAsync(chatHistory, option, _kernel, cancellationToken); + + await foreach (var content in response) + { + if (content.ChoiceIndex > 0) + { + throw new InvalidOperationException("Only one choice is supported in streaming response"); + } + + yield return new MessageEnvelope(content, from: this.Name); + } + } + + private ChatHistory BuildChatHistory(IEnumerable messages) + { + var chatMessageContents = ProcessMessage(messages); + // if there's no system message in chatMessageContents, add one to the beginning + if (!chatMessageContents.Any(c => c.Role == AuthorRole.System)) + { + chatMessageContents = new[] { new ChatMessageContent(AuthorRole.System, _systemMessage) }.Concat(chatMessageContents); + } + + return new ChatHistory(chatMessageContents); + } + + private PromptExecutionSettings BuildOption(GenerateReplyOptions? options) + { + return _settings ?? new OpenAIPromptExecutionSettings + { + Temperature = options?.Temperature ?? 0.7f, + MaxTokens = options?.MaxToken ?? 1024, + StopSequences = options?.StopSequence, + ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions, + ResultsPerPrompt = 1, + }; + } + + private IEnumerable ProcessMessage(IEnumerable messages) + { + return messages.Select(m => m switch + { + IMessage cmc => cmc.Content, + _ => throw new ArgumentException("Invalid message type") + }); + } +} diff --git a/dotnet/src/AutoGen.SemanticKernel/SemanticKernelChatCompletionAgent.cs b/dotnet/src/AutoGen.SemanticKernel/SemanticKernelChatCompletionAgent.cs new file mode 100644 index 00000000000..82d83a9e855 --- /dev/null +++ b/dotnet/src/AutoGen.SemanticKernel/SemanticKernelChatCompletionAgent.cs @@ -0,0 +1,51 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// SemanticKernelChatCompletionAgent.cs + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.Agents; +using Microsoft.SemanticKernel.ChatCompletion; + +namespace AutoGen.SemanticKernel; + +public class SemanticKernelChatCompletionAgent : IAgent +{ + public string Name { get; } + private readonly ChatCompletionAgent _chatCompletionAgent; + + public SemanticKernelChatCompletionAgent(ChatCompletionAgent chatCompletionAgent) + { + this.Name = chatCompletionAgent.Name ?? throw new ArgumentNullException(nameof(chatCompletionAgent.Name)); + this._chatCompletionAgent = chatCompletionAgent; + } + + public async Task GenerateReplyAsync(IEnumerable messages, GenerateReplyOptions? options = null, + CancellationToken cancellationToken = default) + { + ChatMessageContent[] reply = await _chatCompletionAgent + .InvokeAsync(BuildChatHistory(messages), cancellationToken) + .ToArrayAsync(cancellationToken: cancellationToken); + + return reply.Length > 1 + ? throw new InvalidOperationException("ResultsPerPrompt greater than 1 is not supported in this semantic kernel agent") + : new MessageEnvelope(reply[0], from: this.Name); + } + + private ChatHistory BuildChatHistory(IEnumerable messages) + { + return new ChatHistory(ProcessMessage(messages)); + } + + private IEnumerable ProcessMessage(IEnumerable messages) + { + return messages.Select(m => m switch + { + IMessage cmc => cmc.Content, + _ => throw new ArgumentException("Invalid message type") + }); + } +} diff --git a/dotnet/src/AutoGen.SourceGenerator/AutoGen.SourceGenerator.csproj b/dotnet/src/AutoGen.SourceGenerator/AutoGen.SourceGenerator.csproj new file mode 100644 index 00000000000..37f344ed11e --- /dev/null +++ b/dotnet/src/AutoGen.SourceGenerator/AutoGen.SourceGenerator.csproj @@ -0,0 +1,64 @@ +ο»Ώ + + + netstandard2.0 + false + + true + + 35954224-b94e-4024-b0ef-7ba7cf80c0d8 + $(GetTargetPathDependsOn);GetDependencyTargetPaths + false + $(NoWarn);NU5128 + $(DefineConstants);LAUNCH_DEBUGGER + + + + + + + AutoGen.SourceGenerator + Source generator for AutoGen. This package provides type-safe function call to AutoGen agents. + + + + + + + + + + + + + + + + + + + + + + + TextTemplatingFilePreprocessor + FunctionCallTemplate.cs + + + + + + + + + + + + + + True + True + FunctionCallTemplate.tt + + + diff --git a/dotnet/src/AutoGen.SourceGenerator/DocumentCommentExtension.cs b/dotnet/src/AutoGen.SourceGenerator/DocumentCommentExtension.cs new file mode 100644 index 00000000000..a09c77c2d75 --- /dev/null +++ b/dotnet/src/AutoGen.SourceGenerator/DocumentCommentExtension.cs @@ -0,0 +1,295 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// DocumentCommentExtension.cs + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +// copyright: https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/DocumentationCommentExtensions.cs#L17 +namespace AutoGen.SourceGenerator +{ + internal static class DocumentCommentExtension + { + public static bool IsMissingOrDefault(this SyntaxToken token) + { + return token.IsKind(SyntaxKind.None) + || token.IsMissing; + } + + public static string? GetParameterDescriptionFromDocumentationCommentTriviaSyntax(this DocumentationCommentTriviaSyntax documentationCommentTrivia, string parameterName) + { + var parameterElements = documentationCommentTrivia.Content.GetXmlElements("param"); + + var parameter = parameterElements.FirstOrDefault(element => + { + var xml = XElement.Parse(element.ToString()); + var nameAttribute = xml.Attribute("name"); + return nameAttribute != null && nameAttribute.Value == parameterName; + }); + + if (parameter is not null) + { + var xml = XElement.Parse(parameter.ToString()); + + return xml.Nodes().OfType().FirstOrDefault()?.Value; + } + + return null; + } + + public static string? GetNamespaceNameFromClassDeclarationSyntax(this ClassDeclarationSyntax classDeclaration) + { + return classDeclaration.Parent is NamespaceDeclarationSyntax namespaceDeclarationSyntax ? namespaceDeclarationSyntax.Name.ToString() + : classDeclaration.Parent is FileScopedNamespaceDeclarationSyntax fileScopedNamespaceDeclarationSyntax ? fileScopedNamespaceDeclarationSyntax.Name.ToString() + : null; + } + + public static DocumentationCommentTriviaSyntax? GetDocumentationCommentTriviaSyntax(this SyntaxNode node) + { + if (node == null) + { + return null; + } + + foreach (var leadingTrivia in node.GetLeadingTrivia()) + { + if (leadingTrivia.GetStructure() is DocumentationCommentTriviaSyntax structure) + { + return structure; + } + } + + return null; + } + + public static XmlNodeSyntax GetFirstXmlElement(this SyntaxList content, string elementName) + { + return content.GetXmlElements(elementName).FirstOrDefault(); + } + + public static IEnumerable GetXmlElements(this SyntaxList content, string elementName) + { + foreach (XmlNodeSyntax syntax in content) + { + if (syntax is XmlEmptyElementSyntax emptyElement) + { + if (string.Equals(elementName, emptyElement.Name.ToString(), StringComparison.Ordinal)) + { + yield return emptyElement; + } + + continue; + } + + if (syntax is XmlElementSyntax elementSyntax) + { + if (string.Equals(elementName, elementSyntax.StartTag?.Name?.ToString(), StringComparison.Ordinal)) + { + yield return elementSyntax; + } + + continue; + } + } + } + + public static T ReplaceExteriorTrivia(this T node, SyntaxTrivia trivia) + where T : XmlNodeSyntax + { + // Make sure to include a space after the '///' characters. + SyntaxTrivia triviaWithSpace = SyntaxFactory.DocumentationCommentExterior(trivia.ToString() + " "); + + return node.ReplaceTrivia( + node.DescendantTrivia(descendIntoTrivia: true).Where(i => i.IsKind(SyntaxKind.DocumentationCommentExteriorTrivia)), + (originalTrivia, rewrittenTrivia) => SelectExteriorTrivia(rewrittenTrivia, trivia, triviaWithSpace)); + } + + public static SyntaxList WithoutFirstAndLastNewlines(this SyntaxList summaryContent) + { + if (summaryContent.Count == 0) + { + return summaryContent; + } + + if (!(summaryContent[0] is XmlTextSyntax firstSyntax)) + { + return summaryContent; + } + + if (!(summaryContent[summaryContent.Count - 1] is XmlTextSyntax lastSyntax)) + { + return summaryContent; + } + + SyntaxTokenList firstSyntaxTokens = firstSyntax.TextTokens; + + int removeFromStart; + if (IsXmlNewLine(firstSyntaxTokens[0])) + { + removeFromStart = 1; + } + else + { + if (!IsXmlWhitespace(firstSyntaxTokens[0])) + { + return summaryContent; + } + + if (!IsXmlNewLine(firstSyntaxTokens[1])) + { + return summaryContent; + } + + removeFromStart = 2; + } + + SyntaxTokenList lastSyntaxTokens = lastSyntax.TextTokens; + + int removeFromEnd; + if (IsXmlNewLine(lastSyntaxTokens[lastSyntaxTokens.Count - 1])) + { + removeFromEnd = 1; + } + else + { + if (!IsXmlWhitespace(lastSyntaxTokens[lastSyntaxTokens.Count - 1])) + { + return summaryContent; + } + + if (!IsXmlNewLine(lastSyntaxTokens[lastSyntaxTokens.Count - 2])) + { + return summaryContent; + } + + removeFromEnd = 2; + } + + for (int i = 0; i < removeFromStart; i++) + { + firstSyntaxTokens = firstSyntaxTokens.RemoveAt(0); + } + + if (firstSyntax == lastSyntax) + { + lastSyntaxTokens = firstSyntaxTokens; + } + + for (int i = 0; i < removeFromEnd; i++) + { + if (!lastSyntaxTokens.Any()) + { + break; + } + + lastSyntaxTokens = lastSyntaxTokens.RemoveAt(lastSyntaxTokens.Count - 1); + } + + summaryContent = summaryContent.RemoveAt(summaryContent.Count - 1); + if (lastSyntaxTokens.Count != 0) + { + summaryContent = summaryContent.Add(lastSyntax.WithTextTokens(lastSyntaxTokens)); + } + + if (firstSyntax != lastSyntax) + { + summaryContent = summaryContent.RemoveAt(0); + if (firstSyntaxTokens.Count != 0) + { + summaryContent = summaryContent.Insert(0, firstSyntax.WithTextTokens(firstSyntaxTokens)); + } + } + + if (summaryContent.Count > 0) + { + // Make sure to remove the leading trivia + summaryContent = summaryContent.Replace(summaryContent[0], summaryContent[0].WithLeadingTrivia()); + + // Remove leading spaces (between the start tag and the start of the paragraph content) + if (summaryContent[0] is XmlTextSyntax firstTextSyntax && firstTextSyntax.TextTokens.Count > 0) + { + SyntaxToken firstTextToken = firstTextSyntax.TextTokens[0]; + string firstTokenText = firstTextToken.Text; + string trimmed = firstTokenText.TrimStart(); + if (trimmed != firstTokenText) + { + SyntaxToken newFirstToken = SyntaxFactory.Token( + firstTextToken.LeadingTrivia, + firstTextToken.Kind(), + trimmed, + firstTextToken.ValueText.TrimStart(), + firstTextToken.TrailingTrivia); + + summaryContent = summaryContent.Replace(firstTextSyntax, firstTextSyntax.ReplaceToken(firstTextToken, newFirstToken)); + } + } + } + + return summaryContent; + } + + public static bool IsXmlNewLine(this SyntaxToken node) + { + return node.IsKind(SyntaxKind.XmlTextLiteralNewLineToken); + } + + public static bool IsXmlWhitespace(this SyntaxToken node) + { + return node.IsKind(SyntaxKind.XmlTextLiteralToken) + && string.IsNullOrWhiteSpace(node.Text); + } + + /// + /// Adjust the leading and trailing trivia associated with + /// tokens to ensure the formatter properly indents the exterior trivia. + /// + /// The type of syntax node. + /// The syntax node to adjust tokens. + /// A equivalent to the input , adjusted by moving any + /// trailing trivia from tokens to be leading trivia of the + /// following token. + public static T AdjustDocumentationCommentNewLineTrivia(this T node) + where T : SyntaxNode + { + var tokensForAdjustment = + from token in node.DescendantTokens() + where token.IsKind(SyntaxKind.XmlTextLiteralNewLineToken) + where token.HasTrailingTrivia + let next = token.GetNextToken(includeZeroWidth: true, includeSkipped: true, includeDirectives: true, includeDocumentationComments: true) + where !next.IsMissingOrDefault() + select new KeyValuePair(token, next); + + Dictionary replacements = new Dictionary(); + foreach (var pair in tokensForAdjustment) + { + replacements[pair.Key] = pair.Key.WithTrailingTrivia(); + replacements[pair.Value] = pair.Value.WithLeadingTrivia(pair.Value.LeadingTrivia.InsertRange(0, pair.Key.TrailingTrivia)); + } + + return node.ReplaceTokens(replacements.Keys, (originalToken, rewrittenToken) => replacements[originalToken]); + } + + public static XmlNameSyntax? GetName(this XmlNodeSyntax element) + { + return (element as XmlElementSyntax)?.StartTag?.Name + ?? (element as XmlEmptyElementSyntax)?.Name; + } + + private static SyntaxTrivia SelectExteriorTrivia(SyntaxTrivia rewrittenTrivia, SyntaxTrivia trivia, SyntaxTrivia triviaWithSpace) + { + // if the trivia had a trailing space, make sure to preserve it + if (rewrittenTrivia.ToString().EndsWith(" ")) + { + return triviaWithSpace; + } + + // otherwise the space is part of the leading trivia of the following token, so don't add an extra one to + // the exterior trivia + return trivia; + } + } +} diff --git a/dotnet/src/AutoGen.SourceGenerator/FunctionCallGenerator.cs b/dotnet/src/AutoGen.SourceGenerator/FunctionCallGenerator.cs new file mode 100644 index 00000000000..cd01416182b --- /dev/null +++ b/dotnet/src/AutoGen.SourceGenerator/FunctionCallGenerator.cs @@ -0,0 +1,248 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// FunctionCallGenerator.cs + +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using AutoGen.SourceGenerator.Template; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; +using Newtonsoft.Json; + +namespace AutoGen.SourceGenerator +{ + [Generator] + public partial class FunctionCallGenerator : IIncrementalGenerator + { + private const string FUNCTION_CALL_ATTRIBUTION = "AutoGen.Core.FunctionAttribute"; + + public void Initialize(IncrementalGeneratorInitializationContext context) + { +#if LAUNCH_DEBUGGER + if (!System.Diagnostics.Debugger.IsAttached) + { + System.Diagnostics.Debugger.Launch(); + } +#endif + var optionProvider = context.AnalyzerConfigOptionsProvider.Select((provider, ct) => + { + var generateFunctionDefinitionContract = provider.GlobalOptions.TryGetValue("build_property.EnableContract", out var value) && value?.ToLowerInvariant() == "true"; + + return generateFunctionDefinitionContract; + }); + // step 1 + // filter syntax tree and search syntax node that satisfied the following conditions + // - is partial class + var partialClassSyntaxProvider = context.SyntaxProvider.CreateSyntaxProvider( + (node, ct) => + { + return node is ClassDeclarationSyntax classDeclarationSyntax && classDeclarationSyntax.Modifiers.Any(SyntaxKind.PartialKeyword); + }, + (ctx, ct) => + { + // first check if any method of the class has FunctionAttribution attribute + // if not, then return null + var filePath = ctx.Node.SyntaxTree.FilePath; + var fileName = Path.GetFileNameWithoutExtension(filePath); + + + var classDeclarationSyntax = ctx.Node as ClassDeclarationSyntax; + var nameSpace = classDeclarationSyntax?.Parent as NamespaceDeclarationSyntax; + var fullClassName = $"{nameSpace?.Name}.{classDeclarationSyntax!.Identifier}"; + if (classDeclarationSyntax == null) + { + return null; + } + + if (!classDeclarationSyntax.Members.Any(member => member.AttributeLists.Any(attributeList => attributeList.Attributes.Any(attribute => + { + return ctx.SemanticModel.GetSymbolInfo(attribute).Symbol is IMethodSymbol methodSymbol && methodSymbol.ContainingType.ToDisplayString() == FUNCTION_CALL_ATTRIBUTION; + })))) + { + return null; + } + + // collect methods that has FunctionAttribution attribute + var methodDeclarationSyntaxes = classDeclarationSyntax.Members.Where(member => member.AttributeLists.Any(attributeList => attributeList.Attributes.Any(attribute => + { + return ctx.SemanticModel.GetSymbolInfo(attribute).Symbol is IMethodSymbol methodSymbol && methodSymbol.ContainingType.ToDisplayString() == FUNCTION_CALL_ATTRIBUTION; + }))) + .Select(member => member as MethodDeclarationSyntax) + .Where(method => method != null); + + var className = classDeclarationSyntax.Identifier.ToString(); + var namespaceName = classDeclarationSyntax.GetNamespaceNameFromClassDeclarationSyntax(); + var functionContracts = methodDeclarationSyntaxes.Select(method => CreateFunctionContract(method!, className, namespaceName)); + + return new PartialClassOutput(fullClassName, classDeclarationSyntax, functionContracts); + }) + .Where(node => node != null) + .Collect(); + + var aggregateProvider = optionProvider.Combine(partialClassSyntaxProvider); + // step 2 + context.RegisterSourceOutput(aggregateProvider, + (ctx, source) => + { + var groups = source.Right.GroupBy(item => item!.FullClassName); + foreach (var group in groups) + { + var functionContracts = group.SelectMany(item => item!.FunctionContracts).ToArray(); + var className = group.First()!.ClassDeclarationSyntax.Identifier.ToString(); + var namespaceName = group.First()!.ClassDeclarationSyntax.GetNamespaceNameFromClassDeclarationSyntax() ?? string.Empty; + var functionTT = new FunctionCallTemplate + { + NameSpace = namespaceName, + ClassName = className, + FunctionContracts = functionContracts.ToArray(), + }; + + var functionSource = functionTT.TransformText(); + var fileName = $"{className}.generated.cs"; + + ctx.AddSource(fileName, SourceText.From(functionSource, System.Text.Encoding.UTF8)); + File.WriteAllText(Path.Combine(Path.GetTempPath(), fileName), functionSource); + } + + if (source.Left) + { + var overallFunctionDefinition = source.Right.SelectMany(x => x!.FunctionContracts.Select(y => new { fullClassName = x.FullClassName, y = y })); + var overallFunctionDefinitionObject = overallFunctionDefinition.Select( + x => new + { + fullClassName = x.fullClassName, + functionDefinition = new + { + x.y.Name, + x.y.Description, + x.y.ReturnType, + Parameters = x.y.Parameters.Select(y => new + { + y.Name, + y.Description, + y.JsonType, + y.JsonItemType, + y.Type, + y.IsOptional, + y.DefaultValue, + }), + }, + }); + + var json = JsonConvert.SerializeObject(overallFunctionDefinitionObject, formatting: Formatting.Indented); + // wrap json inside csharp block, as SG doesn't support generating non-source file + json = $@"/* wrap json inside csharp block, as SG doesn't support generating non-source file +{json} +*/"; + ctx.AddSource("FunctionDefinition.json", SourceText.From(json, System.Text.Encoding.UTF8)); + } + }); + } + + private class PartialClassOutput + { + public PartialClassOutput(string fullClassName, ClassDeclarationSyntax classDeclarationSyntax, IEnumerable functionContracts) + { + FullClassName = fullClassName; + ClassDeclarationSyntax = classDeclarationSyntax; + FunctionContracts = functionContracts; + } + + public string FullClassName { get; } + + public ClassDeclarationSyntax ClassDeclarationSyntax { get; } + + public IEnumerable FunctionContracts { get; } + } + + private SourceGeneratorFunctionContract CreateFunctionContract(MethodDeclarationSyntax method, string? className, string? namespaceName) + { + // get function_call attribute + var functionCallAttribute = method.AttributeLists.SelectMany(attributeList => attributeList.Attributes) + .FirstOrDefault(attribute => attribute.Name.ToString() == FUNCTION_CALL_ATTRIBUTION); + // get document string if exist + var documentationCommentTrivia = method.GetDocumentationCommentTriviaSyntax(); + + var functionName = method.Identifier.ToString(); + var functionDescription = functionCallAttribute?.ArgumentList?.Arguments.FirstOrDefault(argument => argument.NameEquals?.Name.ToString() == "Description")?.Expression.ToString() ?? string.Empty; + + if (string.IsNullOrEmpty(functionDescription)) + { + // if functionDescription is empty, then try to get it from documentationCommentTrivia + // firstly, try getting from tag + var summary = documentationCommentTrivia?.Content.GetFirstXmlElement("summary"); + if (summary is not null && XElement.Parse(summary.ToString()) is XElement element) + { + functionDescription = element.Nodes().OfType().FirstOrDefault()?.Value; + + // remove [space...][//|///][space...] from functionDescription + // replace [^\S\r\n]+[\/]+\s* with empty string + functionDescription = System.Text.RegularExpressions.Regex.Replace(functionDescription, @"[^\S\r\n]+\/[\/]+\s*", string.Empty); + } + else + { + // if tag is not exist, then simply use the entire leading trivia as functionDescription + functionDescription = method.GetLeadingTrivia().ToString(); + + // remove [space...][//|///][space...] from functionDescription + // replace [^\S\r\n]+[\/]+\s* with empty string + functionDescription = System.Text.RegularExpressions.Regex.Replace(functionDescription, @"[^\S\r\n]+\/[\/]+\s*", string.Empty); + } + } + + // get parameters + var parameters = method.ParameterList.Parameters.Select(parameter => + { + var description = $"{parameter.Identifier}. type is {parameter.Type}"; + + // try to get parameter description from documentationCommentTrivia + var parameterDocumentationComment = documentationCommentTrivia?.GetParameterDescriptionFromDocumentationCommentTriviaSyntax(parameter.Identifier.ToString()); + if (parameterDocumentationComment is not null) + { + description = parameterDocumentationComment.ToString(); + // remove [space...][//|///][space...] from functionDescription + // replace [^\S\r\n]+[\/]+\s* with empty string + description = System.Text.RegularExpressions.Regex.Replace(description, @"[^\S\r\n]+\/[\/]+\s*", string.Empty); + } + var jsonItemType = parameter.Type!.ToString().EndsWith("[]") ? parameter.Type!.ToString().Substring(0, parameter.Type!.ToString().Length - 2) : null; + return new SourceGeneratorParameterContract + { + Name = parameter.Identifier.ToString(), + JsonType = parameter.Type!.ToString() switch + { + "string" => "string", + "string[]" => "array", + "System.Int32" or "int" => "integer", + "System.Int64" or "long" => "integer", + "System.Single" or "float" => "number", + "System.Double" or "double" => "number", + "System.Boolean" or "bool" => "boolean", + "System.DateTime" => "string", + "System.Guid" => "string", + "System.Object" => "object", + _ => "object", + }, + JsonItemType = jsonItemType, + Type = parameter.Type!.ToString(), + Description = description, + IsOptional = parameter.Default != null, + // if Default is null or "null", then DefaultValue is null + DefaultValue = parameter.Default?.ToString() == "null" ? null : parameter.Default?.Value.ToString(), + }; + }); + + return new SourceGeneratorFunctionContract + { + ClassName = className, + Namespace = namespaceName, + Name = functionName, + Description = functionDescription?.Trim() ?? functionName, + Parameters = parameters.ToArray(), + ReturnType = method.ReturnType.ToString(), + }; + } + } +} diff --git a/dotnet/src/AutoGen.SourceGenerator/FunctionExtension.cs b/dotnet/src/AutoGen.SourceGenerator/FunctionExtension.cs new file mode 100644 index 00000000000..cfb77d26a2b --- /dev/null +++ b/dotnet/src/AutoGen.SourceGenerator/FunctionExtension.cs @@ -0,0 +1,32 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// FunctionExtension.cs + +using AutoGen.SourceGenerator; + +internal static class FunctionExtension +{ + public static string GetFunctionName(this SourceGeneratorFunctionContract function) + { + return function.Name ?? string.Empty; + } + + public static string GetFunctionSchemaClassName(this SourceGeneratorFunctionContract function) + { + return $"{function.GetFunctionName()}Schema"; + } + + public static string GetFunctionDefinitionName(this SourceGeneratorFunctionContract function) + { + return $"{function.GetFunctionName()}Function"; + } + + public static string GetFunctionWrapperName(this SourceGeneratorFunctionContract function) + { + return $"{function.GetFunctionName()}Wrapper"; + } + + public static string GetFunctionContractName(this SourceGeneratorFunctionContract function) + { + return $"{function.GetFunctionName()}FunctionContract"; + } +} diff --git a/dotnet/src/AutoGen.SourceGenerator/README.md b/dotnet/src/AutoGen.SourceGenerator/README.md new file mode 100644 index 00000000000..a40fbe60407 --- /dev/null +++ b/dotnet/src/AutoGen.SourceGenerator/README.md @@ -0,0 +1,113 @@ +### AutoGen.SourceGenerator + +This package carries a source generator that adds support for type-safe function definition generation. Simply mark a method with `Function` attribute, and the source generator will generate a function definition and a function call wrapper for you. + +### Get start + +First, add the following to your project file and set `GenerateDocumentationFile` property to true + +```xml + + + true + +``` +```xml + + + +``` + +> Nightly Build feed: https://devdiv.pkgs.visualstudio.com/DevDiv/_packaging/AutoGen/nuget/v3/index.json + +Then, for the methods you want to generate function definition and function call wrapper, mark them with `Function` attribute: + +> Note: For the best of performance, try using primitive types for the parameters and return type. + +```csharp +// file: MyFunctions.cs + +using AutoGen; + +// a partial class is required +// and the class must be public +public partial class MyFunctions +{ + /// + /// Add two numbers. + /// + /// The first number. + /// The second number. + [Function] + public Task AddAsync(int a, int b) + { + return Task.FromResult($"{a} + {b} = {a + b}"); + } +} +``` + +The source generator will generate the following code based on the method signature and documentation. It helps you save the effort of writing function definition and keep it up to date with the actual method signature. + +```csharp +// file: MyFunctions.generated.cs +public partial class MyFunctions +{ + private class AddAsyncSchema + { + public int a {get; set;} + public int b {get; set;} + } + + public Task AddAsyncWrapper(string arguments) + { + var schema = JsonSerializer.Deserialize( + arguments, + new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + }); + return AddAsync(schema.a, schema.b); + } + + public FunctionDefinition AddAsyncFunction + { + get => new FunctionDefinition + { + Name = @"AddAsync", + Description = """ +Add two numbers. +""", + Parameters = BinaryData.FromObjectAsJson(new + { + Type = "object", + Properties = new + { + a = new + { + Type = @"number", + Description = @"The first number.", + }, + b = new + { + Type = @"number", + Description = @"The second number.", + }, + }, + Required = new [] + { + "a", + "b", + }, + }, + new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + }) + }; + } +} +``` + +For more examples, please check out the following project +- [AutoGen.BasicSamples](../sample/AutoGen.BasicSamples/) +- [AutoGen.SourceGenerator.Tests](../../test/AutoGen.SourceGenerator.Tests/) diff --git a/dotnet/src/AutoGen.SourceGenerator/SourceGeneratorFunctionContract.cs b/dotnet/src/AutoGen.SourceGenerator/SourceGeneratorFunctionContract.cs new file mode 100644 index 00000000000..24e42affa3b --- /dev/null +++ b/dotnet/src/AutoGen.SourceGenerator/SourceGeneratorFunctionContract.cs @@ -0,0 +1,40 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// FunctionContract.cs + +namespace AutoGen.SourceGenerator +{ + internal class SourceGeneratorFunctionContract + { + public string? Namespace { get; set; } + + public string? ClassName { get; set; } + + public string? Name { get; set; } + + public string? Description { get; set; } + + public string? ReturnDescription { get; set; } + + public SourceGeneratorParameterContract[]? Parameters { get; set; } + + public string? ReturnType { get; set; } + } + + internal class SourceGeneratorParameterContract + { + public string? Name { get; set; } + + public string? Description { get; set; } + + public string? JsonType { get; set; } + + public string? JsonItemType { get; set; } + + public string? Type { get; set; } + + public bool IsOptional { get; set; } + + public string? DefaultValue { get; set; } + + } +} diff --git a/dotnet/src/AutoGen.SourceGenerator/Template/FunctionCallTemplate.cs b/dotnet/src/AutoGen.SourceGenerator/Template/FunctionCallTemplate.cs new file mode 100644 index 00000000000..e56db112eb7 --- /dev/null +++ b/dotnet/src/AutoGen.SourceGenerator/Template/FunctionCallTemplate.cs @@ -0,0 +1,447 @@ +ο»Ώ// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version: 17.0.0.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// ------------------------------------------------------------------------------ +namespace AutoGen.SourceGenerator.Template +{ + using System.Linq; + using System.Collections.Generic; + using Microsoft.CodeAnalysis; + using System; + + /// + /// Class to produce the template output + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "17.0.0.0")] + internal partial class FunctionCallTemplate : FunctionCallTemplateBase + { + /// + /// Create the template output + /// + public virtual string TransformText() + { + this.Write("ο»Ώ"); + this.Write(@"//---------------------- +// +// This code was generated by a tool. +// +//---------------------- +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Threading.Tasks; +using System; +using AutoGen.Core; +using AutoGen.OpenAI.Extension; + +"); +if (!String.IsNullOrEmpty(NameSpace)) { + this.Write("namespace "); + this.Write(this.ToStringHelper.ToStringWithCulture(NameSpace)); + this.Write("\r\n{\r\n"); +} + this.Write(" public partial class "); + this.Write(this.ToStringHelper.ToStringWithCulture(ClassName)); + this.Write("\r\n {\r\n"); +foreach (var functionContract in FunctionContracts) { + this.Write("\r\n private class "); + this.Write(this.ToStringHelper.ToStringWithCulture(functionContract.GetFunctionSchemaClassName())); + this.Write("\r\n {\r\n"); +foreach (var parameter in functionContract.Parameters) { +if (parameter.IsOptional) { + this.Write(" [JsonPropertyName(@\""); + this.Write(this.ToStringHelper.ToStringWithCulture(parameter.Name)); + this.Write("\")]\r\n\t\t\tpublic "); + this.Write(this.ToStringHelper.ToStringWithCulture(parameter.Type)); + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(parameter.Name)); + this.Write(" {get; set;} = "); + this.Write(this.ToStringHelper.ToStringWithCulture(parameter.DefaultValue)); + this.Write(";\r\n"); +} else { + this.Write(" [JsonPropertyName(@\""); + this.Write(this.ToStringHelper.ToStringWithCulture(parameter.Name)); + this.Write("\")]\r\n\t\t\tpublic "); + this.Write(this.ToStringHelper.ToStringWithCulture(parameter.Type)); + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(parameter.Name)); + this.Write(" {get; set;}\r\n"); +} +} + this.Write(" }\r\n\r\n public "); + this.Write(this.ToStringHelper.ToStringWithCulture(functionContract.ReturnType)); + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(functionContract.GetFunctionWrapperName())); + this.Write("(string arguments)\r\n {\r\n var schema = JsonSerializer.Deserializ" + + "e<"); + this.Write(this.ToStringHelper.ToStringWithCulture(functionContract.GetFunctionSchemaClassName())); + this.Write(">(\r\n arguments, \r\n new JsonSerializerOptions\r\n " + + " {\r\n PropertyNamingPolicy = JsonNamingPolicy.CamelC" + + "ase,\r\n });\r\n"); + var argumentLists = string.Join(", ", functionContract.Parameters.Select(p => $"schema.{p.Name}")); + this.Write("\r\n return "); + this.Write(this.ToStringHelper.ToStringWithCulture(functionContract.Name)); + this.Write("("); + this.Write(this.ToStringHelper.ToStringWithCulture(argumentLists)); + this.Write(");\r\n }\r\n\r\n public FunctionContract "); + this.Write(this.ToStringHelper.ToStringWithCulture(functionContract.GetFunctionContractName())); + this.Write("\r\n {\r\n get => new FunctionContract\r\n {\r\n"); +if (functionContract.Namespace != null) { + this.Write(" Namespace = @\""); + this.Write(this.ToStringHelper.ToStringWithCulture(functionContract.Namespace)); + this.Write("\",\r\n"); +} +if (functionContract.ClassName != null) { + this.Write(" ClassName = @\""); + this.Write(this.ToStringHelper.ToStringWithCulture(functionContract.ClassName)); + this.Write("\",\r\n"); +} +if (functionContract.Name != null) { + this.Write(" Name = @\""); + this.Write(this.ToStringHelper.ToStringWithCulture(functionContract.Name)); + this.Write("\",\r\n"); +} +if (functionContract.Description != null) { + this.Write(" Description = @\""); + this.Write(this.ToStringHelper.ToStringWithCulture(functionContract.Description)); + this.Write("\",\r\n"); +} +if (functionContract.ReturnType != null) { + this.Write(" ReturnType = typeof("); + this.Write(this.ToStringHelper.ToStringWithCulture(functionContract.ReturnType)); + this.Write("),\r\n"); +} +if (functionContract.ReturnDescription != null) { + this.Write(" ReturnDescription = @\""); + this.Write(this.ToStringHelper.ToStringWithCulture(functionContract.ReturnDescription)); + this.Write("\",\r\n"); +} +if (functionContract.Parameters != null) { + this.Write(" Parameters = new []\r\n {\r\n"); +foreach (var parameter in functionContract.Parameters) { + this.Write(" new FunctionParameterContract\r\n {\r\n"); +if (parameter.Name != null) { + this.Write(" Name = @\""); + this.Write(this.ToStringHelper.ToStringWithCulture(parameter.Name)); + this.Write("\",\r\n"); +} +if (parameter.Description != null) { + this.Write(" Description = @\""); + this.Write(this.ToStringHelper.ToStringWithCulture(parameter.Description)); + this.Write("\",\r\n"); +} +if (parameter.Type != null) { + this.Write(" ParameterType = typeof("); + this.Write(this.ToStringHelper.ToStringWithCulture(parameter.Type)); + this.Write("),\r\n"); +} + this.Write(" IsRequired = "); + this.Write(this.ToStringHelper.ToStringWithCulture(parameter.IsOptional ? "false" : "true")); + this.Write(",\r\n"); +if (parameter.DefaultValue != null) { + this.Write(" DefaultValue = "); + this.Write(this.ToStringHelper.ToStringWithCulture(parameter.DefaultValue)); + this.Write(",\r\n"); +} + this.Write(" },\r\n"); +} + this.Write(" },\r\n"); +} + this.Write(" };\r\n }\r\n\r\n public global::Azure.AI.OpenAI.FunctionDefin" + + "ition "); + this.Write(this.ToStringHelper.ToStringWithCulture(functionContract.GetFunctionDefinitionName())); + this.Write("\r\n {\r\n get => this."); + this.Write(this.ToStringHelper.ToStringWithCulture(functionContract.GetFunctionContractName())); + this.Write(".ToOpenAIFunctionDefinition();\r\n }\r\n"); +} + this.Write(" }\r\n"); +if (!String.IsNullOrEmpty(NameSpace)) { + this.Write("}\r\n"); +} + this.Write("\r\n"); + return this.GenerationEnvironment.ToString(); + } + +public string NameSpace {get; set;} +public string ClassName {get; set;} +public IEnumerable FunctionContracts {get; set;} +public bool IsStatic {get; set;} = false; + + } + #region Base class + /// + /// Base class for this transformation + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "17.0.0.0")] + internal class FunctionCallTemplateBase + { + #region Fields + private global::System.Text.StringBuilder generationEnvironmentField; + private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; + private global::System.Collections.Generic.List indentLengthsField; + private string currentIndentField = ""; + private bool endsWithNewline; + private global::System.Collections.Generic.IDictionary sessionField; + #endregion + #region Properties + /// + /// The string builder that generation-time code is using to assemble generated output + /// + public System.Text.StringBuilder GenerationEnvironment + { + get + { + if ((this.generationEnvironmentField == null)) + { + this.generationEnvironmentField = new global::System.Text.StringBuilder(); + } + return this.generationEnvironmentField; + } + set + { + this.generationEnvironmentField = value; + } + } + /// + /// The error collection for the generation process + /// + public System.CodeDom.Compiler.CompilerErrorCollection Errors + { + get + { + if ((this.errorsField == null)) + { + this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); + } + return this.errorsField; + } + } + /// + /// A list of the lengths of each indent that was added with PushIndent + /// + private System.Collections.Generic.List indentLengths + { + get + { + if ((this.indentLengthsField == null)) + { + this.indentLengthsField = new global::System.Collections.Generic.List(); + } + return this.indentLengthsField; + } + } + /// + /// Gets the current indent we use when adding lines to the output + /// + public string CurrentIndent + { + get + { + return this.currentIndentField; + } + } + /// + /// Current transformation session + /// + public virtual global::System.Collections.Generic.IDictionary Session + { + get + { + return this.sessionField; + } + set + { + this.sessionField = value; + } + } + #endregion + #region Transform-time helpers + /// + /// Write text directly into the generated output + /// + public void Write(string textToAppend) + { + if (string.IsNullOrEmpty(textToAppend)) + { + return; + } + // If we're starting off, or if the previous text ended with a newline, + // we have to append the current indent first. + if (((this.GenerationEnvironment.Length == 0) + || this.endsWithNewline)) + { + this.GenerationEnvironment.Append(this.currentIndentField); + this.endsWithNewline = false; + } + // Check if the current text ends with a newline + if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) + { + this.endsWithNewline = true; + } + // This is an optimization. If the current indent is "", then we don't have to do any + // of the more complex stuff further down. + if ((this.currentIndentField.Length == 0)) + { + this.GenerationEnvironment.Append(textToAppend); + return; + } + // Everywhere there is a newline in the text, add an indent after it + textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); + // If the text ends with a newline, then we should strip off the indent added at the very end + // because the appropriate indent will be added when the next time Write() is called + if (this.endsWithNewline) + { + this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); + } + else + { + this.GenerationEnvironment.Append(textToAppend); + } + } + /// + /// Write text directly into the generated output + /// + public void WriteLine(string textToAppend) + { + this.Write(textToAppend); + this.GenerationEnvironment.AppendLine(); + this.endsWithNewline = true; + } + /// + /// Write formatted text directly into the generated output + /// + public void Write(string format, params object[] args) + { + this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Write formatted text directly into the generated output + /// + public void WriteLine(string format, params object[] args) + { + this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); + } + /// + /// Raise an error + /// + public void Error(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + this.Errors.Add(error); + } + /// + /// Raise a warning + /// + public void Warning(string message) + { + System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); + error.ErrorText = message; + error.IsWarning = true; + this.Errors.Add(error); + } + /// + /// Increase the indent + /// + public void PushIndent(string indent) + { + if ((indent == null)) + { + throw new global::System.ArgumentNullException("indent"); + } + this.currentIndentField = (this.currentIndentField + indent); + this.indentLengths.Add(indent.Length); + } + /// + /// Remove the last indent that was added with PushIndent + /// + public string PopIndent() + { + string returnValue = ""; + if ((this.indentLengths.Count > 0)) + { + int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; + this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); + if ((indentLength > 0)) + { + returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); + this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); + } + } + return returnValue; + } + /// + /// Remove any indentation + /// + public void ClearIndent() + { + this.indentLengths.Clear(); + this.currentIndentField = ""; + } + #endregion + #region ToString Helpers + /// + /// Utility class to produce culture-oriented representation of an object as a string. + /// + public class ToStringInstanceHelper + { + private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; + /// + /// Gets or sets format provider to be used by ToStringWithCulture method. + /// + public System.IFormatProvider FormatProvider + { + get + { + return this.formatProviderField ; + } + set + { + if ((value != null)) + { + this.formatProviderField = value; + } + } + } + /// + /// This is called from the compile/run appdomain to convert objects within an expression block to a string + /// + public string ToStringWithCulture(object objectToConvert) + { + if ((objectToConvert == null)) + { + throw new global::System.ArgumentNullException("objectToConvert"); + } + System.Type t = objectToConvert.GetType(); + System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { + typeof(System.IFormatProvider)}); + if ((method == null)) + { + return objectToConvert.ToString(); + } + else + { + return ((string)(method.Invoke(objectToConvert, new object[] { + this.formatProviderField }))); + } + } + } + private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); + /// + /// Helper to produce culture-oriented representation of an object as a string + /// + public ToStringInstanceHelper ToStringHelper + { + get + { + return this.toStringHelperField; + } + } + #endregion + } + #endregion +} diff --git a/dotnet/src/AutoGen.SourceGenerator/Template/FunctionCallTemplate.tt b/dotnet/src/AutoGen.SourceGenerator/Template/FunctionCallTemplate.tt new file mode 100644 index 00000000000..526dfe400ce --- /dev/null +++ b/dotnet/src/AutoGen.SourceGenerator/Template/FunctionCallTemplate.tt @@ -0,0 +1,115 @@ +ο»Ώο»Ώ<#@ template language="C#" linePragmas="false" visibility = "internal" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ import namespace="Microsoft.CodeAnalysis" #> +//---------------------- +// +// This code was generated by a tool. +// +//---------------------- +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Threading.Tasks; +using System; +using AutoGen.Core; +using AutoGen.OpenAI.Extension; + +<#if (!String.IsNullOrEmpty(NameSpace)) {#> +namespace <#=NameSpace#> +{ +<#}#> + public partial class <#=ClassName#> + { +<#foreach (var functionContract in FunctionContracts) {#> + + private class <#=functionContract.GetFunctionSchemaClassName()#> + { +<#foreach (var parameter in functionContract.Parameters) {#> +<#if (parameter.IsOptional) {#> + [JsonPropertyName(@"<#=parameter.Name#>")] + public <#=parameter.Type#> <#=parameter.Name#> {get; set;} = <#=parameter.DefaultValue#>; +<#} else {#> + [JsonPropertyName(@"<#=parameter.Name#>")] + public <#=parameter.Type#> <#=parameter.Name#> {get; set;} +<#}#> +<#}#> + } + + public <#=functionContract.ReturnType#> <#=functionContract.GetFunctionWrapperName()#>(string arguments) + { + var schema = JsonSerializer.Deserialize<<#=functionContract.GetFunctionSchemaClassName()#>>( + arguments, + new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + }); +<# var argumentLists = string.Join(", ", functionContract.Parameters.Select(p => $"schema.{p.Name}")); #> + + return <#=functionContract.Name#>(<#=argumentLists#>); + } + + public FunctionContract <#=functionContract.GetFunctionContractName()#> + { + get => new FunctionContract + { +<#if (functionContract.Namespace != null) {#> + Namespace = @"<#=functionContract.Namespace#>", +<#}#> +<#if (functionContract.ClassName != null) {#> + ClassName = @"<#=functionContract.ClassName#>", +<#}#> +<#if (functionContract.Name != null) {#> + Name = @"<#=functionContract.Name#>", +<#}#> +<#if (functionContract.Description != null) {#> + Description = @"<#=functionContract.Description#>", +<#}#> +<#if (functionContract.ReturnType != null) {#> + ReturnType = typeof(<#=functionContract.ReturnType#>), +<#}#> +<#if (functionContract.ReturnDescription != null) {#> + ReturnDescription = @"<#=functionContract.ReturnDescription#>", +<#}#> +<#if (functionContract.Parameters != null) {#> + Parameters = new [] + { +<#foreach (var parameter in functionContract.Parameters) {#> + new FunctionParameterContract + { +<#if (parameter.Name != null) {#> + Name = @"<#=parameter.Name#>", +<#}#> +<#if (parameter.Description != null) {#> + Description = @"<#=parameter.Description#>", +<#}#> +<#if (parameter.Type != null) {#> + ParameterType = typeof(<#=parameter.Type#>), +<#}#> + IsRequired = <#=parameter.IsOptional ? "false" : "true"#>, +<#if (parameter.DefaultValue != null) {#> + DefaultValue = <#=parameter.DefaultValue#>, +<#}#> + }, +<#}#> + }, +<#}#> + }; + } + + public global::Azure.AI.OpenAI.FunctionDefinition <#=functionContract.GetFunctionDefinitionName()#> + { + get => this.<#=functionContract.GetFunctionContractName()#>.ToOpenAIFunctionDefinition(); + } +<#}#> + } +<#if (!String.IsNullOrEmpty(NameSpace)) {#> +} +<#}#> + +<#+ +public string NameSpace {get; set;} +public string ClassName {get; set;} +public IEnumerable FunctionContracts {get; set;} +public bool IsStatic {get; set;} = false; +#> \ No newline at end of file diff --git a/dotnet/src/AutoGen/API/LLMConfigAPI.cs b/dotnet/src/AutoGen/API/LLMConfigAPI.cs new file mode 100644 index 00000000000..5154f3dd5f5 --- /dev/null +++ b/dotnet/src/AutoGen/API/LLMConfigAPI.cs @@ -0,0 +1,50 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// LLMConfigAPI.cs + +using System; +using System.Collections.Generic; +using System.Linq; +using AutoGen.OpenAI; + +namespace AutoGen +{ + public static class LLMConfigAPI + { + public static IEnumerable GetOpenAIConfigList( + string apiKey, + IEnumerable? modelIDs = null) + { + var models = modelIDs ?? new[] + { + "gpt-3.5-turbo", + "gpt-3.5-turbo-16k", + "gpt-4", + "gpt-4-32k", + "gpt-4-0613", + "gpt-4-32k-0613", + "gpt-4-1106-preview", + }; + + return models.Select(modelId => new OpenAIConfig(apiKey, modelId)); + } + + public static IEnumerable GetAzureOpenAIConfigList( + string endpoint, + string apiKey, + IEnumerable deploymentNames) + { + return deploymentNames.Select(deploymentName => new AzureOpenAIConfig(endpoint, deploymentName, apiKey)); + } + + /// + /// Get a list of LLMConfig objects from a JSON file. + /// + internal static IEnumerable ConfigListFromJson( + string filePath, + IEnumerable? filterModels = null) + { + // Disable this API from documentation for now. + throw new NotImplementedException(); + } + } +} diff --git a/dotnet/src/AutoGen/Agent/AssistantAgent.cs b/dotnet/src/AutoGen/Agent/AssistantAgent.cs new file mode 100644 index 00000000000..06f65042add --- /dev/null +++ b/dotnet/src/AutoGen/Agent/AssistantAgent.cs @@ -0,0 +1,30 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// AssistantAgent.cs + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace AutoGen; + +public class AssistantAgent : ConversableAgent +{ + public AssistantAgent( + string name, + string systemMessage = "You are a helpful AI assistant", + ConversableAgentConfig? llmConfig = null, + Func, CancellationToken, Task>? isTermination = null, + HumanInputMode humanInputMode = HumanInputMode.NEVER, + IDictionary>>? functionMap = null, + string? defaultReply = null) + : base(name: name, + systemMessage: systemMessage, + llmConfig: llmConfig, + isTermination: isTermination, + humanInputMode: humanInputMode, + functionMap: functionMap, + defaultReply: defaultReply) + { + } +} diff --git a/dotnet/src/AutoGen/Agent/ConversableAgent.cs b/dotnet/src/AutoGen/Agent/ConversableAgent.cs new file mode 100644 index 00000000000..fe147050202 --- /dev/null +++ b/dotnet/src/AutoGen/Agent/ConversableAgent.cs @@ -0,0 +1,181 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// ConversableAgent.cs + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using AutoGen.LMStudio; +using AutoGen.OpenAI; + +namespace AutoGen; + +public enum HumanInputMode +{ + /// + /// NEVER prompt the user for input + /// + NEVER = 0, + + /// + /// ALWAYS prompt the user for input + /// + ALWAYS = 1, + + /// + /// prompt the user for input if the message is not a termination message + /// + AUTO = 2, +} + +public class ConversableAgent : IAgent +{ + private readonly IAgent? innerAgent; + private readonly string? defaultReply; + private readonly HumanInputMode humanInputMode; + private readonly IDictionary>>? functionMap; + private readonly string systemMessage; + private readonly IEnumerable? functions; + + public ConversableAgent( + string name, + string systemMessage = "You are a helpful AI assistant", + IAgent? innerAgent = null, + string? defaultAutoReply = null, + HumanInputMode humanInputMode = HumanInputMode.NEVER, + Func, CancellationToken, Task>? isTermination = null, + IDictionary>>? functionMap = null) + { + this.Name = name; + this.defaultReply = defaultAutoReply; + this.functionMap = functionMap; + this.humanInputMode = humanInputMode; + this.innerAgent = innerAgent; + this.IsTermination = isTermination; + this.systemMessage = systemMessage; + } + + public ConversableAgent( + string name, + string systemMessage = "You are a helpful AI assistant", + ConversableAgentConfig? llmConfig = null, + Func, CancellationToken, Task>? isTermination = null, + HumanInputMode humanInputMode = HumanInputMode.AUTO, + IDictionary>>? functionMap = null, + string? defaultReply = null) + { + this.Name = name; + this.defaultReply = defaultReply; + this.functionMap = functionMap; + this.humanInputMode = humanInputMode; + this.IsTermination = isTermination; + this.systemMessage = systemMessage; + this.innerAgent = llmConfig?.ConfigList != null ? this.CreateInnerAgentFromConfigList(llmConfig) : null; + this.functions = llmConfig?.FunctionContracts; + } + + /// + /// For test purpose only. + /// + internal IAgent? InnerAgent => this.innerAgent; + + private IAgent? CreateInnerAgentFromConfigList(ConversableAgentConfig config) + { + IAgent? agent = null; + foreach (var llmConfig in config.ConfigList ?? Enumerable.Empty()) + { + IAgent nextAgent = llmConfig switch + { + AzureOpenAIConfig azureConfig => new GPTAgent(this.Name!, this.systemMessage, azureConfig, temperature: config.Temperature ?? 0), + OpenAIConfig openAIConfig => new GPTAgent(this.Name!, this.systemMessage, openAIConfig, temperature: config.Temperature ?? 0), + LMStudioConfig lmStudioConfig => new LMStudioAgent( + name: this.Name, + config: lmStudioConfig, + systemMessage: this.systemMessage, + temperature: config.Temperature ?? 0), + _ => throw new ArgumentException($"Unsupported config type {llmConfig.GetType()}"), + }; + + if (agent == null) + { + agent = nextAgent; + } + else + { + agent = agent.RegisterMiddleware(async (messages, option, agent, cancellationToken) => + { + var agentResponse = await nextAgent.GenerateReplyAsync(messages, option, cancellationToken: cancellationToken); + + if (agentResponse is null) + { + return await agent.GenerateReplyAsync(messages, option, cancellationToken); + } + else + { + return agentResponse; + } + }); + } + } + + return agent; + } + + public string Name { get; } + + public Func, CancellationToken, Task>? IsTermination { get; } + + public async Task GenerateReplyAsync( + IEnumerable messages, + GenerateReplyOptions? overrideOptions = null, + CancellationToken cancellationToken = default) + { + // if there's no system message, add system message to the first of chat history + if (!messages.Any(m => m.IsSystemMessage())) + { + var systemMessage = new TextMessage(Role.System, this.systemMessage, from: this.Name); + messages = new[] { systemMessage }.Concat(messages); + } + + // process order: function_call -> human_input -> inner_agent -> default_reply -> self_execute + // first in, last out + + // process default reply + MiddlewareAgent agent; + if (this.innerAgent != null) + { + agent = innerAgent.RegisterMiddleware(async (msgs, option, agent, ct) => + { + var updatedMessages = msgs.Select(m => + { + if (m.From == this.Name) + { + m.From = this.innerAgent.Name; + return m; + } + else + { + return m; + } + }); + + return await agent.GenerateReplyAsync(updatedMessages, option, ct); + }); + } + else + { + agent = new MiddlewareAgent(new DefaultReplyAgent(this.Name!, this.defaultReply ?? "Default reply is not set. Please pass a default reply to assistant agent")); + } + + // process human input + var humanInputMiddleware = new HumanInputMiddleware(mode: this.humanInputMode, isTermination: this.IsTermination); + agent.Use(humanInputMiddleware); + + // process function call + var functionCallMiddleware = new FunctionCallMiddleware(functions: this.functions, functionMap: this.functionMap); + agent.Use(functionCallMiddleware); + + return await agent.GenerateReplyAsync(messages, overrideOptions, cancellationToken); + } +} diff --git a/dotnet/src/AutoGen/Agent/UserProxyAgent.cs b/dotnet/src/AutoGen/Agent/UserProxyAgent.cs new file mode 100644 index 00000000000..a48f07006b8 --- /dev/null +++ b/dotnet/src/AutoGen/Agent/UserProxyAgent.cs @@ -0,0 +1,30 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// UserProxyAgent.cs + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace AutoGen; + +public class UserProxyAgent : ConversableAgent +{ + public UserProxyAgent( + string name, + string systemMessage = "You are a helpful AI assistant", + ConversableAgentConfig? llmConfig = null, + Func, CancellationToken, Task>? isTermination = null, + HumanInputMode humanInputMode = HumanInputMode.ALWAYS, + IDictionary>>? functionMap = null, + string? defaultReply = null) + : base(name: name, + systemMessage: systemMessage, + llmConfig: llmConfig, + isTermination: isTermination, + humanInputMode: humanInputMode, + functionMap: functionMap, + defaultReply: defaultReply) + { + } +} diff --git a/dotnet/src/AutoGen/AutoGen.csproj b/dotnet/src/AutoGen/AutoGen.csproj new file mode 100644 index 00000000000..7e14ad4e73f --- /dev/null +++ b/dotnet/src/AutoGen/AutoGen.csproj @@ -0,0 +1,34 @@ +ο»Ώ + + netstandard2.0 + AutoGen + + + + + + + AutoGen + + The all-in-one package for AutoGen. This package provides contracts, core functionalities, OpenAI integration, source generator, etc. for AutoGen. + + + + + + + + + + + + + + + + + + + + + diff --git a/dotnet/src/AutoGen/ConversableAgentConfig.cs b/dotnet/src/AutoGen/ConversableAgentConfig.cs new file mode 100644 index 00000000000..50a83ba8620 --- /dev/null +++ b/dotnet/src/AutoGen/ConversableAgentConfig.cs @@ -0,0 +1,17 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// ConversableAgentConfig.cs + +using System.Collections.Generic; + +namespace AutoGen; + +public class ConversableAgentConfig +{ + public IEnumerable? FunctionContracts { get; set; } + + public IEnumerable? ConfigList { get; set; } + + public float? Temperature { get; set; } = 0.7f; + + public int? Timeout { get; set; } +} diff --git a/dotnet/src/AutoGen/GlobalUsing.cs b/dotnet/src/AutoGen/GlobalUsing.cs new file mode 100644 index 00000000000..d66bf001ed5 --- /dev/null +++ b/dotnet/src/AutoGen/GlobalUsing.cs @@ -0,0 +1,4 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// GlobalUsing.cs + +global using AutoGen.Core; diff --git a/dotnet/src/AutoGen/Middleware/HumanInputMiddleware.cs b/dotnet/src/AutoGen/Middleware/HumanInputMiddleware.cs new file mode 100644 index 00000000000..1a742b11c79 --- /dev/null +++ b/dotnet/src/AutoGen/Middleware/HumanInputMiddleware.cs @@ -0,0 +1,97 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// HumanInputMiddleware.cs + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace AutoGen; + +/// +/// the middleware to get human input +/// +public class HumanInputMiddleware : IMiddleware +{ + private readonly HumanInputMode mode; + private readonly string prompt; + private readonly string exitKeyword; + private Func, CancellationToken, Task> isTermination; + private Func getInput = Console.ReadLine; + private Action writeLine = Console.WriteLine; + public string? Name => nameof(HumanInputMiddleware); + + public HumanInputMiddleware( + string prompt = "Please give feedback: Press enter or type 'exit' to stop the conversation.", + string exitKeyword = "exit", + HumanInputMode mode = HumanInputMode.AUTO, + Func, CancellationToken, Task>? isTermination = null, + Func? getInput = null, + Action? writeLine = null) + { + this.prompt = prompt; + this.isTermination = isTermination ?? DefaultIsTermination; + this.exitKeyword = exitKeyword; + this.mode = mode; + this.getInput = getInput ?? GetInput; + this.writeLine = writeLine ?? WriteLine; + } + + public async Task InvokeAsync(MiddlewareContext context, IAgent agent, CancellationToken cancellationToken = default) + { + // if the mode is never, then just return the input message + if (mode == HumanInputMode.NEVER) + { + return await agent.GenerateReplyAsync(context.Messages, context.Options, cancellationToken); + } + + // if the mode is always, then prompt the user for input + if (mode == HumanInputMode.ALWAYS) + { + this.writeLine(prompt); + var input = getInput(); + if (input == exitKeyword) + { + return new TextMessage(Role.Assistant, GroupChatExtension.TERMINATE, agent.Name); + } + + return new TextMessage(Role.Assistant, input, agent.Name); + } + + // if the mode is auto, then prompt the user for input if the message is not a termination message + if (mode == HumanInputMode.AUTO) + { + if (await isTermination(context.Messages, cancellationToken) is false) + { + return await agent.GenerateReplyAsync(context.Messages, context.Options, cancellationToken); + } + + this.writeLine(prompt); + var input = getInput(); + if (input == exitKeyword) + { + return new TextMessage(Role.Assistant, GroupChatExtension.TERMINATE, agent.Name); + } + + return new TextMessage(Role.Assistant, input, agent.Name); + } + + throw new InvalidOperationException("Invalid mode"); + } + + private async Task DefaultIsTermination(IEnumerable messages, CancellationToken _) + { + return messages?.Last().IsGroupChatTerminateMessage() is true; + } + + private string GetInput() + { + return Console.ReadLine(); + } + + private void WriteLine(string message) + { + Console.WriteLine(message); + } +} diff --git a/dotnet/src/Autogen.Ollama/Agent/OllamaAgent.cs b/dotnet/src/Autogen.Ollama/Agent/OllamaAgent.cs new file mode 100644 index 00000000000..6f87e20e233 --- /dev/null +++ b/dotnet/src/Autogen.Ollama/Agent/OllamaAgent.cs @@ -0,0 +1,216 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// OllamaAgent.cs + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Runtime.CompilerServices; +using System.Text; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using AutoGen.Core; + +namespace Autogen.Ollama; + +/// +/// An agent that can interact with ollama models. +/// +public class OllamaAgent : IStreamingAgent +{ + private readonly HttpClient _httpClient; + public string Name { get; } + private readonly string _modelName; + private readonly string _systemMessage; + private readonly OllamaReplyOptions? _replyOptions; + + public OllamaAgent(HttpClient httpClient, string name, string modelName, + string systemMessage = "You are a helpful AI assistant", + OllamaReplyOptions? replyOptions = null) + { + Name = name; + _httpClient = httpClient; + _modelName = modelName; + _systemMessage = systemMessage; + _replyOptions = replyOptions; + } + public async Task GenerateReplyAsync( + IEnumerable messages, GenerateReplyOptions? options = null, CancellationToken cancellation = default) + { + ChatRequest request = await BuildChatRequest(messages, options); + request.Stream = false; + using (HttpResponseMessage? response = await _httpClient + .SendAsync(BuildRequestMessage(request), HttpCompletionOption.ResponseContentRead, cancellation)) + { + response.EnsureSuccessStatusCode(); + Stream? streamResponse = await response.Content.ReadAsStreamAsync(); + ChatResponse chatResponse = await JsonSerializer.DeserializeAsync(streamResponse, cancellationToken: cancellation) + ?? throw new Exception("Failed to deserialize response"); + var output = new MessageEnvelope(chatResponse, from: Name); + return output; + } + } + public async IAsyncEnumerable GenerateStreamingReplyAsync( + IEnumerable messages, + GenerateReplyOptions? options = null, + [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + ChatRequest request = await BuildChatRequest(messages, options); + request.Stream = true; + HttpRequestMessage message = BuildRequestMessage(request); + using (HttpResponseMessage? response = await _httpClient.SendAsync(message, HttpCompletionOption.ResponseHeadersRead, cancellationToken)) + { + response.EnsureSuccessStatusCode(); + using Stream? stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + using var reader = new StreamReader(stream); + + while (!reader.EndOfStream && !cancellationToken.IsCancellationRequested) + { + string? line = await reader.ReadLineAsync(); + if (string.IsNullOrWhiteSpace(line)) continue; + + ChatResponseUpdate? update = JsonSerializer.Deserialize(line); + if (update != null) + { + yield return new MessageEnvelope(update, from: Name); + } + + if (update is { Done: false }) continue; + + ChatResponse? chatMessage = JsonSerializer.Deserialize(line); + if (chatMessage == null) continue; + yield return new MessageEnvelope(chatMessage, from: Name); + } + } + } + private async Task BuildChatRequest(IEnumerable messages, GenerateReplyOptions? options) + { + var request = new ChatRequest + { + Model = _modelName, + Messages = await BuildChatHistory(messages) + }; + + if (options is OllamaReplyOptions replyOptions) + { + BuildChatRequestOptions(replyOptions, request); + return request; + } + + if (_replyOptions != null) + { + BuildChatRequestOptions(_replyOptions, request); + return request; + } + return request; + } + private void BuildChatRequestOptions(OllamaReplyOptions replyOptions, ChatRequest request) + { + request.Format = replyOptions.Format == FormatType.Json ? OllamaConsts.JsonFormatType : null; + request.Template = replyOptions.Template; + request.KeepAlive = replyOptions.KeepAlive; + + if (replyOptions.Temperature != null + || replyOptions.MaxToken != null + || replyOptions.StopSequence != null + || replyOptions.Seed != null + || replyOptions.MiroStat != null + || replyOptions.MiroStatEta != null + || replyOptions.MiroStatTau != null + || replyOptions.NumCtx != null + || replyOptions.NumGqa != null + || replyOptions.NumGpu != null + || replyOptions.NumThread != null + || replyOptions.RepeatLastN != null + || replyOptions.RepeatPenalty != null + || replyOptions.TopK != null + || replyOptions.TopP != null + || replyOptions.TfsZ != null) + { + request.Options = new ModelReplyOptions + { + Temperature = replyOptions.Temperature, + NumPredict = replyOptions.MaxToken, + Stop = replyOptions.StopSequence?[0], + Seed = replyOptions.Seed, + MiroStat = replyOptions.MiroStat, + MiroStatEta = replyOptions.MiroStatEta, + MiroStatTau = replyOptions.MiroStatTau, + NumCtx = replyOptions.NumCtx, + NumGqa = replyOptions.NumGqa, + NumGpu = replyOptions.NumGpu, + NumThread = replyOptions.NumThread, + RepeatLastN = replyOptions.RepeatLastN, + RepeatPenalty = replyOptions.RepeatPenalty, + TopK = replyOptions.TopK, + TopP = replyOptions.TopP, + TfsZ = replyOptions.TfsZ + }; + } + } + private async Task> BuildChatHistory(IEnumerable messages) + { + if (!messages.Any(m => m.IsSystemMessage())) + { + var systemMessage = new TextMessage(Role.System, _systemMessage, from: Name); + messages = new[] { systemMessage }.Concat(messages); + } + + var collection = new List(); + foreach (IMessage? message in messages) + { + Message item; + switch (message) + { + case TextMessage tm: + item = new Message { Role = tm.Role.ToString(), Value = tm.Content }; + break; + case ImageMessage im: + string base64Image = await ImageUrlToBase64(im.Url!); + item = new Message { Role = im.Role.ToString(), Images = [base64Image] }; + break; + case MultiModalMessage mm: + var textsGroupedByRole = mm.Content.OfType().GroupBy(tm => tm.Role) + .ToDictionary(g => g.Key, g => string.Join(Environment.NewLine, g.Select(tm => tm.Content))); + + string content = string.Join($"{Environment.NewLine}", textsGroupedByRole + .Select(g => $"{g.Key}{Environment.NewLine}:{g.Value}")); + + IEnumerable> imagesConversionTasks = mm.Content + .OfType() + .Select(async im => await ImageUrlToBase64(im.Url!)); + + string[]? imagesBase64 = await Task.WhenAll(imagesConversionTasks); + item = new Message { Role = mm.Role.ToString(), Value = content, Images = imagesBase64 }; + break; + default: + throw new NotSupportedException(); + } + + collection.Add(item); + } + + return collection; + } + private static HttpRequestMessage BuildRequestMessage(ChatRequest request) + { + string serialized = JsonSerializer.Serialize(request); + return new HttpRequestMessage(HttpMethod.Post, OllamaConsts.ChatCompletionEndpoint) + { + Content = new StringContent(serialized, Encoding.UTF8, OllamaConsts.JsonMediaType) + }; + } + private async Task ImageUrlToBase64(string imageUrl) + { + if (string.IsNullOrWhiteSpace(imageUrl)) + { + throw new ArgumentException("required parameter", nameof(imageUrl)); + } + byte[] imageBytes = await _httpClient.GetByteArrayAsync(imageUrl); + return imageBytes != null + ? Convert.ToBase64String(imageBytes) + : throw new InvalidOperationException("no image byte array"); + } +} diff --git a/dotnet/src/Autogen.Ollama/Autogen.Ollama.csproj b/dotnet/src/Autogen.Ollama/Autogen.Ollama.csproj new file mode 100644 index 00000000000..9a01f95ca8e --- /dev/null +++ b/dotnet/src/Autogen.Ollama/Autogen.Ollama.csproj @@ -0,0 +1,12 @@ +ο»Ώ + + + netstandard2.0 + True + + + + + + + diff --git a/dotnet/src/Autogen.Ollama/DTOs/ChatRequest.cs b/dotnet/src/Autogen.Ollama/DTOs/ChatRequest.cs new file mode 100644 index 00000000000..a48fb42cfbf --- /dev/null +++ b/dotnet/src/Autogen.Ollama/DTOs/ChatRequest.cs @@ -0,0 +1,54 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// ChatRequest.cs + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Autogen.Ollama; + +public class ChatRequest +{ + /// + /// (required) the model name + /// + [JsonPropertyName("model")] + public string Model { get; set; } = string.Empty; + + /// + /// the messages of the chat, this can be used to keep a chat memory + /// + [JsonPropertyName("messages")] + public IList Messages { get; set; } = Array.Empty(); + + /// + /// the format to return a response in. Currently, the only accepted value is json + /// + [JsonPropertyName("format")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? Format { get; set; } + + /// + /// additional model parameters listed in the documentation for the Modelfile such as temperature + /// + [JsonPropertyName("options")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public ModelReplyOptions? Options { get; set; } + /// + /// the prompt template to use (overrides what is defined in the Modelfile) + /// + [JsonPropertyName("template")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? Template { get; set; } + /// + /// if false the response will be returned as a single response object, rather than a stream of objects + /// + [JsonPropertyName("stream")] + public bool Stream { get; set; } + /// + /// controls how long the model will stay loaded into memory following the request (default: 5m) + /// + [JsonPropertyName("keep_alive")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? KeepAlive { get; set; } +} diff --git a/dotnet/src/Autogen.Ollama/DTOs/ChatResponse.cs b/dotnet/src/Autogen.Ollama/DTOs/ChatResponse.cs new file mode 100644 index 00000000000..2de150f7235 --- /dev/null +++ b/dotnet/src/Autogen.Ollama/DTOs/ChatResponse.cs @@ -0,0 +1,45 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// ChatResponse.cs + +using System.Text.Json.Serialization; + +namespace Autogen.Ollama; + +public class ChatResponse : ChatResponseUpdate +{ + /// + /// time spent generating the response + /// + [JsonPropertyName("total_duration")] + public long TotalDuration { get; set; } + + /// + /// time spent in nanoseconds loading the model + /// + [JsonPropertyName("load_duration")] + public long LoadDuration { get; set; } + + /// + /// number of tokens in the prompt + /// + [JsonPropertyName("prompt_eval_count")] + public int PromptEvalCount { get; set; } + + /// + /// time spent in nanoseconds evaluating the prompt + /// + [JsonPropertyName("prompt_eval_duration")] + public long PromptEvalDuration { get; set; } + + /// + /// number of tokens the response + /// + [JsonPropertyName("eval_count")] + public int EvalCount { get; set; } + + /// + /// time in nanoseconds spent generating the response + /// + [JsonPropertyName("eval_duration")] + public long EvalDuration { get; set; } +} diff --git a/dotnet/src/Autogen.Ollama/DTOs/ChatResponseUpdate.cs b/dotnet/src/Autogen.Ollama/DTOs/ChatResponseUpdate.cs new file mode 100644 index 00000000000..181dacfc34b --- /dev/null +++ b/dotnet/src/Autogen.Ollama/DTOs/ChatResponseUpdate.cs @@ -0,0 +1,42 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// ChatResponseUpdate.cs + +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Autogen.Ollama; + +public class ChatResponseUpdate +{ + [JsonPropertyName("model")] + public string Model { get; set; } = string.Empty; + + [JsonPropertyName("created_at")] + public string CreatedAt { get; set; } = string.Empty; + + [JsonPropertyName("message")] + public Message? Message { get; set; } + + [JsonPropertyName("done")] + public bool Done { get; set; } +} + +public class Message +{ + /// + /// the role of the message, either system, user or assistant + /// + [JsonPropertyName("role")] + public string Role { get; set; } = string.Empty; + /// + /// the content of the message + /// + [JsonPropertyName("content")] + public string Value { get; set; } = string.Empty; + + /// + /// (optional): a list of images to include in the message (for multimodal models such as llava) + /// + [JsonPropertyName("images")] + public IList? Images { get; set; } +} diff --git a/dotnet/src/Autogen.Ollama/DTOs/ModelReplyOptions.cs b/dotnet/src/Autogen.Ollama/DTOs/ModelReplyOptions.cs new file mode 100644 index 00000000000..d7854b77b20 --- /dev/null +++ b/dotnet/src/Autogen.Ollama/DTOs/ModelReplyOptions.cs @@ -0,0 +1,129 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// ModelReplyOptions.cs + +using System.Text.Json.Serialization; + +namespace Autogen.Ollama; + +//https://github.com/ollama/ollama/blob/main/docs/modelfile.md#valid-parameters-and-values +public class ModelReplyOptions +{ + /// + /// Enable Mirostat sampling for controlling perplexity. (default: 0, 0 = disabled, 1 = Mirostat, 2 = Mirostat 2.0) + /// + [JsonPropertyName("mirostat")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public int? MiroStat { get; set; } + + /// + /// Influences how quickly the algorithm responds to feedback from the generated text. + /// A lower learning rate will result in slower adjustments, while a higher learning rate will make the algorithm more responsive. (Default: 0.1) + /// + [JsonPropertyName("mirostat_eta")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public float? MiroStatEta { get; set; } + + /// + /// Controls the balance between coherence and diversity of the output. + /// A lower value will result in more focused and coherent text. (Default: 5.0) + /// + [JsonPropertyName("mirostat_tau")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public float? MiroStatTau { get; set; } + + /// + /// Sets the size of the context window used to generate the next token. (Default: 2048) + /// + [JsonPropertyName("num_ctx")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public int? NumCtx { get; set; } + + /// + /// The number of GQA groups in the transformer layer. Required for some models, for example it is 8 for llama2:70b + /// + [JsonPropertyName("num_gqa")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public int? NumGqa { get; set; } + + /// + /// The number of layers to send to the GPU(s). On macOS it defaults to 1 to enable metal support, 0 to disable. + /// + [JsonPropertyName("num_gpu")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public int? NumGpu { get; set; } + + /// + /// Sets the number of threads to use during computation. By default, Ollama will detect this for optimal performance. + /// It is recommended to set this value to the number of physical CPU cores your system has (as opposed to the logical number of cores). + /// + [JsonPropertyName("num_thread")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public int? NumThread { get; set; } + + /// + /// Sets how far back for the model to look back to prevent repetition. (Default: 64, 0 = disabled, -1 = num_ctx) + /// + [JsonPropertyName("repeat_last_n")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public int? RepeatLastN { get; set; } + + /// + /// Sets how strongly to penalize repetitions. + /// A higher value (e.g., 1.5) will penalize repetitions more strongly, while a lower value (e.g., 0.9) will be more lenient. (Default: 1.1) + /// + [JsonPropertyName("repeat_penalty")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public float? RepeatPenalty { get; set; } + + /// + /// The temperature of the model. Increasing the temperature will make the model answer more creatively. (Default: 0.8) + /// + [JsonPropertyName("temperature")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public float? Temperature { get; set; } + + /// + /// Sets the random number seed to use for generation. + /// Setting this to a specific number will make the model generate the same text for the same prompt. (Default: 0) + /// + [JsonPropertyName("seed")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public int? Seed { get; set; } + + /// + /// Sets the stop sequences to use. When this pattern is encountered the LLM will stop generating text and return. + /// Multiple stop patterns may be set by specifying multiple separate stop parameters in a modelfile. + /// + [JsonPropertyName("stop")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? Stop { get; set; } + + /// + /// Tail free sampling is used to reduce the impact of less probable tokens from the output. + /// A higher value (e.g., 2.0) will reduce the impact more, while a value of 1.0 disables this setting. (default: 1) + /// + [JsonPropertyName("tfs_z")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public float? TfsZ { get; set; } + + /// + /// Maximum number of tokens to predict when generating text. (Default: 128, -1 = infinite generation, -2 = fill context) + /// + [JsonPropertyName("num_predict")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public int? NumPredict { get; set; } + + /// + /// Reduces the probability of generating nonsense. A higher value (e.g. 100) will give more diverse answers, while a lower value (e.g. 10) will be more conservative. (Default: 40) + /// + [JsonPropertyName("top_k")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public int? TopK { get; set; } + + /// + /// Works together with top-k. A higher value (e.g., 0.95) will lead to more diverse text, while a lower value (e.g., 0.5) will generate more focused and conservative text. (Default: 0.9) + /// + [JsonPropertyName("top_p")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public int? TopP { get; set; } +} diff --git a/dotnet/src/Autogen.Ollama/DTOs/OllamaReplyOptions.cs b/dotnet/src/Autogen.Ollama/DTOs/OllamaReplyOptions.cs new file mode 100644 index 00000000000..97bf57cb10c --- /dev/null +++ b/dotnet/src/Autogen.Ollama/DTOs/OllamaReplyOptions.cs @@ -0,0 +1,111 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// OllamaReplyOptions.cs + +using AutoGen.Core; + +namespace Autogen.Ollama; + +public enum FormatType +{ + None, + Json +} + +public class OllamaReplyOptions : GenerateReplyOptions +{ + /// + /// the format to return a response in. Currently, the only accepted value is json + /// + public FormatType Format { get; set; } = FormatType.None; + + /// + /// the prompt template to use (overrides what is defined in the Modelfile) + /// + public string? Template { get; set; } + + /// + /// The temperature of the model. Increasing the temperature will make the model answer more creatively. (Default: 0.8) + /// + public new float? Temperature { get; set; } + + /// + /// controls how long the model will stay loaded into memory following the request (default: 5m) + /// + public string? KeepAlive { get; set; } + + /// + /// Enable Mirostat sampling for controlling perplexity. (default: 0, 0 = disabled, 1 = Mirostat, 2 = Mirostat 2.0) + /// + public int? MiroStat { get; set; } + + /// + /// Influences how quickly the algorithm responds to feedback from the generated text. + /// A lower learning rate will result in slower adjustments, while a higher learning rate will make the algorithm more responsive. (Default: 0.1) + /// + public float? MiroStatEta { get; set; } + + /// + /// Controls the balance between coherence and diversity of the output. + /// A lower value will result in more focused and coherent text. (Default: 5.0) + /// + public float? MiroStatTau { get; set; } + + /// + /// Sets the size of the context window used to generate the next token. (Default: 2048) + /// + public int? NumCtx { get; set; } + + /// + /// The number of GQA groups in the transformer layer. Required for some models, for example it is 8 for llama2:70b + /// + public int? NumGqa { get; set; } + + /// + /// The number of layers to send to the GPU(s). On macOS it defaults to 1 to enable metal support, 0 to disable. + /// + public int? NumGpu { get; set; } + + /// + /// Sets the number of threads to use during computation. By default, Ollama will detect this for optimal performance. + /// It is recommended to set this value to the number of physical CPU cores your system has (as opposed to the logical number of cores). + /// + public int? NumThread { get; set; } + + /// + /// Sets how far back for the model to look back to prevent repetition. (Default: 64, 0 = disabled, -1 = num_ctx) + /// + public int? RepeatLastN { get; set; } + + /// + /// Sets how strongly to penalize repetitions. + /// A higher value (e.g., 1.5) will penalize repetitions more strongly, while a lower value (e.g., 0.9) will be more lenient. (Default: 1.1) + /// + public float? RepeatPenalty { get; set; } + + /// + /// Sets the random number seed to use for generation. + /// Setting this to a specific number will make the model generate the same text for the same prompt. (Default: 0) + /// + public int? Seed { get; set; } + + /// + /// Tail free sampling is used to reduce the impact of less probable tokens from the output. + /// A higher value (e.g., 2.0) will reduce the impact more, while a value of 1.0 disables this setting. (default: 1) + /// + public float? TfsZ { get; set; } + + /// + /// Maximum number of tokens to predict when generating text. (Default: 128, -1 = infinite generation, -2 = fill context) + /// + public new int? MaxToken { get; set; } + + /// + /// Reduces the probability of generating nonsense. A higher value (e.g. 100) will give more diverse answers, while a lower value (e.g. 10) will be more conservative. (Default: 40) + /// + public int? TopK { get; set; } + + /// + /// Works together with top-k. A higher value (e.g., 0.95) will lead to more diverse text, while a lower value (e.g., 0.5) will generate more focused and conservative text. (Default: 0.9) + /// + public int? TopP { get; set; } +} diff --git a/dotnet/src/Autogen.Ollama/Embeddings/ITextEmbeddingService.cs b/dotnet/src/Autogen.Ollama/Embeddings/ITextEmbeddingService.cs new file mode 100644 index 00000000000..f1ea1b8406c --- /dev/null +++ b/dotnet/src/Autogen.Ollama/Embeddings/ITextEmbeddingService.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ITextEmbeddingService.cs + +using System.Threading; +using System.Threading.Tasks; + +namespace Autogen.Ollama; + +public interface ITextEmbeddingService +{ + public Task GenerateAsync(TextEmbeddingsRequest request, CancellationToken cancellationToken); +} diff --git a/dotnet/src/Autogen.Ollama/Embeddings/OllamaTextEmbeddingService.cs b/dotnet/src/Autogen.Ollama/Embeddings/OllamaTextEmbeddingService.cs new file mode 100644 index 00000000000..db913377a5f --- /dev/null +++ b/dotnet/src/Autogen.Ollama/Embeddings/OllamaTextEmbeddingService.cs @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// OllamaTextEmbeddingService.cs + +using System; +using System.IO; +using System.Net.Http; +using System.Text; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; + +namespace Autogen.Ollama; + +public class OllamaTextEmbeddingService : ITextEmbeddingService +{ + private readonly HttpClient _client; + + public OllamaTextEmbeddingService(HttpClient client) + { + _client = client; + } + public async Task GenerateAsync(TextEmbeddingsRequest request, CancellationToken cancellationToken = default) + { + using (HttpResponseMessage? response = await _client + .SendAsync(BuildPostRequest(request), HttpCompletionOption.ResponseContentRead, cancellationToken)) + { + response.EnsureSuccessStatusCode(); + + Stream? streamResponse = await response.Content.ReadAsStreamAsync(); + TextEmbeddingsResponse output = await JsonSerializer + .DeserializeAsync(streamResponse, cancellationToken: cancellationToken) + ?? throw new Exception("Failed to deserialize response"); + return output; + } + } + private static HttpRequestMessage BuildPostRequest(TextEmbeddingsRequest request) + { + string serialized = JsonSerializer.Serialize(request); + return new HttpRequestMessage(HttpMethod.Post, OllamaConsts.EmbeddingsEndpoint) + { + Content = new StringContent(serialized, Encoding.UTF8, OllamaConsts.JsonMediaType) + }; + } +} diff --git a/dotnet/src/Autogen.Ollama/Embeddings/TextEmbeddingsRequest.cs b/dotnet/src/Autogen.Ollama/Embeddings/TextEmbeddingsRequest.cs new file mode 100644 index 00000000000..1577dc53643 --- /dev/null +++ b/dotnet/src/Autogen.Ollama/Embeddings/TextEmbeddingsRequest.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// TextEmbeddingsRequest.cs + +using System.Text.Json.Serialization; + +namespace Autogen.Ollama; + +public class TextEmbeddingsRequest +{ + /// + /// name of model to generate embeddings from + /// + [JsonPropertyName("model")] + public string Model { get; set; } = string.Empty; + /// + /// text to generate embeddings for + /// + [JsonPropertyName("prompt")] + public string Prompt { get; set; } = string.Empty; + /// + /// additional model parameters listed in the documentation for the Modelfile such as temperature + /// + [JsonPropertyName("options")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public ModelReplyOptions? Options { get; set; } + /// + /// controls how long the model will stay loaded into memory following the request (default: 5m) + /// + [JsonPropertyName("keep_alive")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? KeepAlive { get; set; } +} diff --git a/dotnet/src/Autogen.Ollama/Embeddings/TextEmbeddingsResponse.cs b/dotnet/src/Autogen.Ollama/Embeddings/TextEmbeddingsResponse.cs new file mode 100644 index 00000000000..eb46359fdb2 --- /dev/null +++ b/dotnet/src/Autogen.Ollama/Embeddings/TextEmbeddingsResponse.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// TextEmbeddingsResponse.cs + +using System.Text.Json.Serialization; + +namespace Autogen.Ollama; + +public class TextEmbeddingsResponse +{ + [JsonPropertyName("embedding")] + public double[]? Embedding { get; set; } +} diff --git a/dotnet/src/Autogen.Ollama/Middlewares/OllamaMessageConnector.cs b/dotnet/src/Autogen.Ollama/Middlewares/OllamaMessageConnector.cs new file mode 100644 index 00000000000..6defedbe02a --- /dev/null +++ b/dotnet/src/Autogen.Ollama/Middlewares/OllamaMessageConnector.cs @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// OllamaMessageConnector.cs + +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using AutoGen.Core; + +namespace Autogen.Ollama; + +public class OllamaMessageConnector : IMiddleware, IStreamingMiddleware +{ + public string Name => nameof(OllamaMessageConnector); + + public async Task InvokeAsync(MiddlewareContext context, IAgent agent, + CancellationToken cancellationToken = default) + { + IEnumerable messages = context.Messages; + IMessage reply = await agent.GenerateReplyAsync(messages, context.Options, cancellationToken); + switch (reply) + { + case IMessage messageEnvelope: + Message? message = messageEnvelope.Content.Message; + return new TextMessage(Role.Assistant, message != null ? message.Value : "EMPTY_CONTENT", messageEnvelope.From); + default: + throw new NotSupportedException(); + } + } + + public async IAsyncEnumerable InvokeAsync(MiddlewareContext context, IStreamingAgent agent, + [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + await foreach (IStreamingMessage? update in agent.GenerateStreamingReplyAsync(context.Messages, context.Options, cancellationToken)) + { + switch (update) + { + case IMessage complete: + { + string? textContent = complete.Content.Message?.Value; + yield return new TextMessage(Role.Assistant, textContent!, complete.From); + break; + } + case IMessage updatedMessage: + { + string? textContent = updatedMessage.Content.Message?.Value; + yield return new TextMessageUpdate(Role.Assistant, textContent, updatedMessage.From); + break; + } + default: + throw new InvalidOperationException("Message type not supported."); + } + } + } +} diff --git a/dotnet/src/Autogen.Ollama/OllamaConsts.cs b/dotnet/src/Autogen.Ollama/OllamaConsts.cs new file mode 100644 index 00000000000..49e91ebc318 --- /dev/null +++ b/dotnet/src/Autogen.Ollama/OllamaConsts.cs @@ -0,0 +1,12 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// OllamaConsts.cs + +namespace Autogen.Ollama; + +public class OllamaConsts +{ + public const string JsonFormatType = "json"; + public const string JsonMediaType = "application/json"; + public const string ChatCompletionEndpoint = "/api/chat"; + public const string EmbeddingsEndpoint = "/api/embeddings"; +} diff --git a/dotnet/test/.editorconfig b/dotnet/test/.editorconfig new file mode 100644 index 00000000000..cc0410613c4 --- /dev/null +++ b/dotnet/test/.editorconfig @@ -0,0 +1,7 @@ +# Suppressing errors for Test projects under test folder +[*.cs] +dotnet_diagnostic.CA2007.severity = none # Do not directly await a Task +dotnet_diagnostic.VSTHRD111.severity = none # Use .ConfigureAwait(bool) is hidden by default, set to none to prevent IDE from changing on autosave +dotnet_diagnostic.CS1591.severity = none # Missing XML comment for publicly visible type or member +dotnet_diagnostic.CS1998.severity = none # Async method lacks 'await' operators and will run synchronously +dotnet_diagnostic.IDE1006.severity = warning # Naming rule violations \ No newline at end of file diff --git a/dotnet/test/AutoGen.DotnetInteractive.Tests/AutoGen.DotnetInteractive.Tests.csproj b/dotnet/test/AutoGen.DotnetInteractive.Tests/AutoGen.DotnetInteractive.Tests.csproj new file mode 100644 index 00000000000..cf2c24eaf78 --- /dev/null +++ b/dotnet/test/AutoGen.DotnetInteractive.Tests/AutoGen.DotnetInteractive.Tests.csproj @@ -0,0 +1,24 @@ +ο»Ώ + + + $(TestTargetFramework) + enable + false + True + + + + + + + + + + + + + + + + + diff --git a/dotnet/test/AutoGen.DotnetInteractive.Tests/DotnetInteractiveServiceTest.cs b/dotnet/test/AutoGen.DotnetInteractive.Tests/DotnetInteractiveServiceTest.cs new file mode 100644 index 00000000000..0e36053c45e --- /dev/null +++ b/dotnet/test/AutoGen.DotnetInteractive.Tests/DotnetInteractiveServiceTest.cs @@ -0,0 +1,82 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// DotnetInteractiveServiceTest.cs + +using FluentAssertions; +using Xunit; +using Xunit.Abstractions; + +namespace AutoGen.DotnetInteractive.Tests; + +public class DotnetInteractiveServiceTest : IDisposable +{ + private ITestOutputHelper _output; + private InteractiveService _interactiveService; + private string _workingDir; + + public DotnetInteractiveServiceTest(ITestOutputHelper output) + { + _output = output; + _workingDir = Path.Combine(Path.GetTempPath(), "test", Path.GetRandomFileName()); + if (!Directory.Exists(_workingDir)) + { + Directory.CreateDirectory(_workingDir); + } + + _interactiveService = new InteractiveService(_workingDir); + _interactiveService.StartAsync(_workingDir, default).Wait(); + } + + public void Dispose() + { + _interactiveService.Dispose(); + } + + [Fact] + public async Task ItRunCSharpCodeSnippetTestsAsync() + { + var cts = new CancellationTokenSource(); + var isRunning = await _interactiveService.StartAsync(_workingDir, cts.Token); + + isRunning.Should().BeTrue(); + + _interactiveService.IsRunning().Should().BeTrue(); + + // test code snippet + var hello_world = @" +Console.WriteLine(""hello world""); +"; + + await this.TestCSharpCodeSnippet(_interactiveService, hello_world, "hello world"); + await this.TestCSharpCodeSnippet( + _interactiveService, + code: @" +Console.WriteLine(""hello world"" +", + expectedOutput: "Error: (2,32): error CS1026: ) expected"); + + await this.TestCSharpCodeSnippet( + service: _interactiveService, + code: "throw new Exception();", + expectedOutput: "Error: System.Exception: Exception of type 'System.Exception' was thrown"); + } + + [Fact] + public async Task ItRunPowershellScriptTestsAsync() + { + // test power shell + var ps = @"Write-Output ""hello world"""; + await this.TestPowershellCodeSnippet(_interactiveService, ps, "hello world"); + } + + private async Task TestPowershellCodeSnippet(InteractiveService service, string code, string expectedOutput) + { + var result = await service.SubmitPowershellCodeAsync(code, CancellationToken.None); + result.Should().StartWith(expectedOutput); + } + + private async Task TestCSharpCodeSnippet(InteractiveService service, string code, string expectedOutput) + { + var result = await service.SubmitCSharpCodeAsync(code, CancellationToken.None); + result.Should().StartWith(expectedOutput); + } +} diff --git a/dotnet/test/AutoGen.Mistral.Tests/AutoGen.Mistral.Tests.csproj b/dotnet/test/AutoGen.Mistral.Tests/AutoGen.Mistral.Tests.csproj new file mode 100644 index 00000000000..eff70486928 --- /dev/null +++ b/dotnet/test/AutoGen.Mistral.Tests/AutoGen.Mistral.Tests.csproj @@ -0,0 +1,26 @@ +ο»Ώ + + + $(TestTargetFramework) + enable + false + True + + + + + + + + + + + + + + + + + + + diff --git a/dotnet/test/AutoGen.Mistral.Tests/MistralClientAgentTests.cs b/dotnet/test/AutoGen.Mistral.Tests/MistralClientAgentTests.cs new file mode 100644 index 00000000000..2b6839dd0ef --- /dev/null +++ b/dotnet/test/AutoGen.Mistral.Tests/MistralClientAgentTests.cs @@ -0,0 +1,237 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// MistralClientAgentTests.cs + +using System.Text.Json; +using AutoGen.Core; +using AutoGen.Mistral.Extension; +using AutoGen.Tests; +using FluentAssertions; +using Xunit.Abstractions; + +namespace AutoGen.Mistral.Tests; + +public partial class MistralClientAgentTests +{ + private ITestOutputHelper _output; + + public MistralClientAgentTests(ITestOutputHelper output) + { + _output = output; + } + + [Function] + public async Task GetWeather(string city) + { + return $"The weather in {city} is sunny."; + } + + [ApiKeyFact("MISTRAL_API_KEY")] + public async Task MistralAgentChatCompletionTestAsync() + { + var apiKey = Environment.GetEnvironmentVariable("MISTRAL_API_KEY") ?? throw new InvalidOperationException("MISTRAL_API_KEY is not set."); + var client = new MistralClient(apiKey: apiKey); + + var agent = new MistralClientAgent( + client: client, + name: "MistralClientAgent", + model: "open-mistral-7b") + .RegisterMessageConnector(); + var singleAgentTest = new SingleAgentTest(_output); + await singleAgentTest.UpperCaseTestAsync(agent); + await singleAgentTest.UpperCaseStreamingTestAsync(agent); + } + + [ApiKeyFact("MISTRAL_API_KEY")] + public async Task MistralAgentJsonModeTestAsync() + { + var apiKey = Environment.GetEnvironmentVariable("MISTRAL_API_KEY") ?? throw new InvalidOperationException("MISTRAL_API_KEY is not set."); + var client = new MistralClient(apiKey: apiKey); + + var agent = new MistralClientAgent( + client: client, + name: "MistralClientAgent", + jsonOutput: true, + systemMessage: "You are a helpful assistant that convert input to json object", + model: "open-mistral-7b", + randomSeed: 0) + .RegisterMessageConnector(); + + var reply = await agent.SendAsync("name: John, age: 41, email: g123456@gmail.com"); + reply.Should().BeOfType(); + reply.GetContent().Should().NotBeNullOrEmpty(); + reply.From.Should().Be(agent.Name); + var json = reply.GetContent(); + var person = JsonSerializer.Deserialize(json!); + + person.Should().NotBeNull(); + person!.Name.Should().Be("John"); + person!.Age.Should().Be(41); + person!.Email.Should().Be("g123456@gmail.com"); + } + + [ApiKeyFact("MISTRAL_API_KEY")] + public async Task MistralAgentFunctionCallMessageTest() + { + var apiKey = Environment.GetEnvironmentVariable("MISTRAL_API_KEY") ?? throw new InvalidOperationException("MISTRAL_API_KEY is not set."); + var client = new MistralClient(apiKey: apiKey); + var agent = new MistralClientAgent( + client: client, + name: "MistralClientAgent", + model: "mistral-small-latest", + randomSeed: 0) + .RegisterMessageConnector(); + + var weatherFunctionArgumets = """ + { + "city": "Seattle" + } + """; + var functionCallResult = await this.GetWeatherWrapper(weatherFunctionArgumets); + + IMessage[] chatHistory = [ + new TextMessage(Role.User, "what's the weather in Seattle?"), + new ToolCallMessage(this.GetWeatherFunctionContract.Name!, weatherFunctionArgumets, from: agent.Name), + new ToolCallResultMessage(functionCallResult, this.GetWeatherFunctionContract.Name!, weatherFunctionArgumets), + ]; + + var reply = await agent.SendAsync(chatHistory: chatHistory); + + reply.Should().BeOfType(); + reply.GetContent().Should().Be("The weather in Seattle is sunny."); + } + + [ApiKeyFact("MISTRAL_API_KEY")] + public async Task MistralAgentTwoAgentFunctionCallTest() + { + var apiKey = Environment.GetEnvironmentVariable("MISTRAL_API_KEY") ?? throw new InvalidOperationException("MISTRAL_API_KEY is not set."); + var client = new MistralClient(apiKey: apiKey); + var twoAgentTest = new TwoAgentTest(_output); + var functionCallMiddleware = new FunctionCallMiddleware( + functions: [twoAgentTest.GetWeatherFunctionContract]); + var functionCallAgent = new MistralClientAgent( + client: client, + name: "MistralClientAgent", + model: "mistral-small-latest", + randomSeed: 0) + .RegisterMessageConnector() + .RegisterStreamingMiddleware(functionCallMiddleware); + + var functionCallMiddlewareExecutorMiddleware = new FunctionCallMiddleware( + functionMap: new Dictionary>> + { + { twoAgentTest.GetWeatherFunctionContract.Name!, twoAgentTest.GetWeatherWrapper } + }); + var executorAgent = new MistralClientAgent( + client: client, + name: "ExecutorAgent", + model: "mistral-small-latest", + randomSeed: 0) + .RegisterMessageConnector() + .RegisterStreamingMiddleware(functionCallMiddlewareExecutorMiddleware); + await twoAgentTest.TwoAgentGetWeatherFunctionCallTestAsync(executorAgent, functionCallAgent); + } + + [ApiKeyFact("MISTRAL_API_KEY")] + public async Task MistralAgentFunctionCallMiddlewareMessageTest() + { + var apiKey = Environment.GetEnvironmentVariable("MISTRAL_API_KEY") ?? throw new InvalidOperationException("MISTRAL_API_KEY is not set."); + var client = new MistralClient(apiKey: apiKey); + var functionCallMiddleware = new FunctionCallMiddleware( + functions: [this.GetWeatherFunctionContract], + functionMap: new Dictionary>> + { + { this.GetWeatherFunctionContract.Name!, this.GetWeatherWrapper } + }); + var functionCallAgent = new MistralClientAgent( + client: client, + name: "MistralClientAgent", + model: "mistral-small-latest", + randomSeed: 0) + .RegisterMessageConnector() + .RegisterStreamingMiddleware(functionCallMiddleware); + + var question = new TextMessage(Role.User, "what's the weather in Seattle?"); + var reply = await functionCallAgent.SendAsync(question); + reply.Should().BeOfType>(); + + // resend the reply to the same agent so it can generate the final response + // because the reply's from is the agent's name + // in this case, the aggregate message will be converted to tool call message + tool call result message + var finalReply = await functionCallAgent.SendAsync(chatHistory: [question, reply]); + finalReply.Should().BeOfType(); + finalReply.GetContent().Should().Be("The weather in Seattle is sunny."); + + var anotherAgent = new MistralClientAgent( + client: client, + name: "AnotherMistralClientAgent", + model: "mistral-small-latest", + randomSeed: 0) + .RegisterMessageConnector(); + + // if send the reply to another agent with different name, + // the reply will be interpreted as a plain text message + var plainTextReply = await anotherAgent.SendAsync(chatHistory: [reply, question]); + plainTextReply.Should().BeOfType(); + } + + [ApiKeyFact("MISTRAL_API_KEY")] + public async Task MistralAgentFunctionCallAutoInvokeTestAsync() + { + var apiKey = Environment.GetEnvironmentVariable("MISTRAL_API_KEY") ?? throw new InvalidOperationException("MISTRAL_API_KEY is not set."); + var client = new MistralClient(apiKey: apiKey); + var singleAgentTest = new SingleAgentTest(_output); + var functionCallMiddleware = new FunctionCallMiddleware( + functions: [singleAgentTest.EchoAsyncFunctionContract], + functionMap: new Dictionary>> + { + { singleAgentTest.EchoAsyncFunctionContract.Name!, singleAgentTest.EchoAsyncWrapper } + }); + var agent = new MistralClientAgent( + client: client, + name: "MistralClientAgent", + model: "mistral-small-latest", + toolChoice: ToolChoiceEnum.Any, + randomSeed: 0) + .RegisterMessageConnector() + .RegisterStreamingMiddleware(functionCallMiddleware); + await singleAgentTest.EchoFunctionCallExecutionTestAsync(agent); + await singleAgentTest.EchoFunctionCallExecutionStreamingTestAsync(agent); + } + + [ApiKeyFact("MISTRAL_API_KEY")] + public async Task MistralAgentFunctionCallTestAsync() + { + var apiKey = Environment.GetEnvironmentVariable("MISTRAL_API_KEY") ?? throw new InvalidOperationException("MISTRAL_API_KEY is not set."); + var client = new MistralClient(apiKey: apiKey); + var singleAgentTest = new SingleAgentTest(_output); + var functionCallMiddleware = new FunctionCallMiddleware( + functions: [singleAgentTest.EchoAsyncFunctionContract, this.GetWeatherFunctionContract]); + var agent = new MistralClientAgent( + client: client, + name: "MistralClientAgent", + model: "mistral-small-latest", + toolChoice: ToolChoiceEnum.Any, + systemMessage: "You are a helpful assistant that can call functions", + randomSeed: 0) + .RegisterMessageConnector() + .RegisterStreamingMiddleware(functionCallMiddleware); + await singleAgentTest.EchoFunctionCallTestAsync(agent); + + + // streaming test + var question = new TextMessage(Role.User, "what's the weather in Seattle?"); + IMessage? finalReply = null; + + await foreach (var reply in agent.GenerateStreamingReplyAsync([question])) + { + reply.From.Should().Be(agent.Name); + if (reply is IMessage message) + { + finalReply = message; + } + } + + finalReply.Should().NotBeNull(); + finalReply.Should().BeOfType(); + } +} diff --git a/dotnet/test/AutoGen.Mistral.Tests/MistralClientTests.cs b/dotnet/test/AutoGen.Mistral.Tests/MistralClientTests.cs new file mode 100644 index 00000000000..bd285adf673 --- /dev/null +++ b/dotnet/test/AutoGen.Mistral.Tests/MistralClientTests.cs @@ -0,0 +1,287 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// MistralClientTests.cs + +using System.Text.Json; +using System.Text.Json.Serialization; +using AutoGen.Core; +using AutoGen.Mistral.Extension; +using AutoGen.Tests; +using FluentAssertions; + +namespace AutoGen.Mistral.Tests; + +public partial class MistralClientTests +{ + [Function] + public async Task GetWeather(string city) + { + return $"The weather in {city} is sunny."; + } + + [ApiKeyFact("MISTRAL_API_KEY")] + public async Task MistralClientChatCompletionTestAsync() + { + var apiKey = Environment.GetEnvironmentVariable("MISTRAL_API_KEY") ?? throw new InvalidOperationException("MISTRAL_API_KEY is not set."); + var client = new MistralClient(apiKey: apiKey); + + var systemMessage = new ChatMessage(ChatMessage.RoleEnum.System, "You are a helpful assistant."); + var userMessage = new ChatMessage(ChatMessage.RoleEnum.User, "What is the weather like today?"); + + var request = new ChatCompletionRequest( + model: "open-mistral-7b", + messages: new List { systemMessage, userMessage }, + temperature: 0); + + var response = await client.CreateChatCompletionsAsync(request); + + response.Choices!.Count().Should().Be(1); + response.Choices!.First().Message!.Content.Should().NotBeNullOrEmpty(); + response.Choices!.First().Message!.Role.Should().Be(ChatMessage.RoleEnum.Assistant); + response.Usage!.TotalTokens.Should().BeGreaterThan(0); + } + + [ApiKeyFact("MISTRAL_API_KEY")] + public async Task MistralClientStreamingChatCompletionTestAsync() + { + var apiKey = Environment.GetEnvironmentVariable("MISTRAL_API_KEY") ?? throw new InvalidOperationException("MISTRAL_API_KEY is not set."); + var client = new MistralClient(apiKey: apiKey); + + var systemMessage = new ChatMessage(ChatMessage.RoleEnum.System, "You are a helpful assistant."); + var userMessage = new ChatMessage(ChatMessage.RoleEnum.User, "What is the weather like today?"); + + var request = new ChatCompletionRequest( + model: "open-mistral-7b", + messages: new List { systemMessage, userMessage }, + temperature: 0); + + var response = client.StreamingChatCompletionsAsync(request); + var results = new List(); + + await foreach (var item in response) + { + results.Add(item); + item.VarObject.Should().Be("chat.completion.chunk"); + } + + results.Count.Should().BeGreaterThan(0); + + // merge result + var finalResult = results.First(); + foreach (var result in results) + { + if (finalResult.Choices!.First().Message is null) + { + finalResult.Choices!.First().Message = result.Choices!.First().Delta; + } + else + { + finalResult.Choices!.First().Message!.Content += result.Choices!.First().Delta!.Content; + } + + // the usage information will be included in the last result + if (result.Usage != null) + { + finalResult.Usage = result.Usage; + } + } + finalResult.Choices!.First().Message!.Content.Should().NotBeNullOrEmpty(); + finalResult.Choices!.First().Message!.Role.Should().Be(ChatMessage.RoleEnum.Assistant); + finalResult.Usage!.TotalTokens.Should().BeGreaterThan(0); + } + + [ApiKeyFact("MISTRAL_API_KEY")] + public async Task MistralClientStreamingChatJsonModeCompletionTestAsync() + { + var apiKey = Environment.GetEnvironmentVariable("MISTRAL_API_KEY") ?? throw new InvalidOperationException("MISTRAL_API_KEY is not set."); + var client = new MistralClient(apiKey: apiKey); + + var systemMessage = new ChatMessage(ChatMessage.RoleEnum.System, "You are a helpful assistant that convert input to json object"); + var userMessage = new ChatMessage(ChatMessage.RoleEnum.User, "name: John, age: 41, email: g123456@gmail.com"); + + var request = new ChatCompletionRequest( + model: "open-mistral-7b", + messages: new List { systemMessage, userMessage }, + temperature: 0) + { + ResponseFormat = new ResponseFormat { ResponseFormatType = "json_object" }, + }; + + var response = client.StreamingChatCompletionsAsync(request); + var results = new List(); + + await foreach (var item in response) + { + results.Add(item); + item.VarObject.Should().Be("chat.completion.chunk"); + } + + results.Count.Should().BeGreaterThan(0); + + // merge result + var finalResult = results.First(); + foreach (var result in results) + { + if (finalResult.Choices!.First().Message is null) + { + finalResult.Choices!.First().Message = result.Choices!.First().Delta; + } + else + { + finalResult.Choices!.First().Message!.Content += result.Choices!.First().Delta!.Content; + } + + // the usage information will be included in the last result + if (result.Usage != null) + { + finalResult.Usage = result.Usage; + } + } + + finalResult.Choices!.First().Message!.Content.Should().NotBeNullOrEmpty(); + finalResult.Choices!.First().Message!.Role.Should().Be(ChatMessage.RoleEnum.Assistant); + finalResult.Usage!.TotalTokens.Should().BeGreaterThan(0); + var responseContent = finalResult.Choices!.First().Message!.Content ?? throw new InvalidOperationException("Response content is null."); + var person = JsonSerializer.Deserialize(responseContent); + person.Should().NotBeNull(); + + person!.Name.Should().Be("John"); + person!.Age.Should().Be(41); + person!.Email.Should().Be("g123456@gmail.com"); + } + + [ApiKeyFact("MISTRAL_API_KEY")] + public async Task MistralClientJsonModeTestAsync() + { + var apiKey = Environment.GetEnvironmentVariable("MISTRAL_API_KEY") ?? throw new InvalidOperationException("MISTRAL_API_KEY is not set."); + var client = new MistralClient(apiKey: apiKey); + + var systemMessage = new ChatMessage(ChatMessage.RoleEnum.System, "You are a helpful assistant that convert input to json object"); + var userMessage = new ChatMessage(ChatMessage.RoleEnum.User, "name: John, age: 41, email: g123456@gmail.com"); + + var request = new ChatCompletionRequest( + model: "open-mistral-7b", + messages: new List { systemMessage, userMessage }, + temperature: 0) + { + ResponseFormat = new ResponseFormat { ResponseFormatType = "json_object" }, + }; + + var response = await client.CreateChatCompletionsAsync(request); + + response.Choices!.Count().Should().Be(1); + response.Choices!.First().Message!.Content.Should().NotBeNullOrEmpty(); + response.Choices!.First().Message!.Role.Should().Be(ChatMessage.RoleEnum.Assistant); + response.Usage!.TotalTokens.Should().BeGreaterThan(0); + + // check if the response is a valid json object + var responseContent = response.Choices!.First().Message!.Content ?? throw new InvalidOperationException("Response content is null."); + var person = JsonSerializer.Deserialize(responseContent); + person.Should().NotBeNull(); + + person!.Name.Should().Be("John"); + person!.Age.Should().Be(41); + person!.Email.Should().Be("g123456@gmail.com"); + } + + + [ApiKeyFact("MISTRAL_API_KEY")] + public async Task MistralClientFunctionCallTestAsync() + { + var apiKey = Environment.GetEnvironmentVariable("MISTRAL_API_KEY") ?? throw new InvalidOperationException("MISTRAL_API_KEY is not set."); + using var client = new MistralClient(apiKey: apiKey); + + var getWeatherFunctionContract = this.GetWeatherFunctionContract; + var functionDefinition = getWeatherFunctionContract.ToMistralFunctionDefinition(); + + var systemMessage = new ChatMessage(ChatMessage.RoleEnum.System, "You are a helpful assistant."); + var userMessage = new ChatMessage(ChatMessage.RoleEnum.User, "What is the weather in Seattle?"); + + var request = new ChatCompletionRequest( + model: "mistral-small-latest", // only large or small latest models support function calls + messages: new List { systemMessage, userMessage }, + temperature: 0) + { + Tools = [new FunctionTool(functionDefinition)], + ToolChoice = ToolChoiceEnum.Any, + }; + + var response = await client.CreateChatCompletionsAsync(request); + + response.Choices!.Count().Should().Be(1); + response.Choices!.First().Message!.Content.Should().BeNullOrEmpty(); + response.Choices!.First().FinishReason.Should().Be(Choice.FinishReasonEnum.ToolCalls); + response.Choices!.First().Message!.ToolCalls!.Count.Should().Be(1); + response.Choices!.First().Message!.ToolCalls!.First().Function.Name.Should().Be("GetWeather"); + } + + [ApiKeyFact("MISTRAL_API_KEY")] + public async Task MistralClientStreamingFunctionCallTestAsync() + { + var apiKey = Environment.GetEnvironmentVariable("MISTRAL_API_KEY") ?? throw new InvalidOperationException("MISTRAL_API_KEY is not set."); + using var client = new MistralClient(apiKey: apiKey); + + var getWeatherFunctionContract = this.GetWeatherFunctionContract; + var functionDefinition = getWeatherFunctionContract.ToMistralFunctionDefinition(); + + var systemMessage = new ChatMessage(ChatMessage.RoleEnum.System, "You are a helpful assistant."); + var userMessage = new ChatMessage(ChatMessage.RoleEnum.User, "What is the weather in Seattle?"); + + var request = new ChatCompletionRequest( + model: "mistral-small-latest", + messages: new List { systemMessage, userMessage }, + temperature: 0) + { + Tools = [new FunctionTool(functionDefinition)], + ToolChoice = ToolChoiceEnum.Any, + }; + + var response = client.StreamingChatCompletionsAsync(request); + + var results = new List(); + await foreach (var item in response) + { + results.Add(item); + item.VarObject.Should().Be("chat.completion.chunk"); + } + + // merge result + var finalResult = results.First(); + var lastResult = results.Last(); + lastResult.Choices!.First().FinishReason.Should().Be(Choice.FinishReasonEnum.ToolCalls); + + foreach (var result in results) + { + if (finalResult.Choices!.First().Message is null) + { + finalResult.Choices!.First().Message = result.Choices!.First().Delta; + finalResult.Choices!.First().Message!.ToolCalls = []; + } + else + { + finalResult.Choices!.First().Message!.ToolCalls = finalResult.Choices!.First().Message!.ToolCalls!.Concat(result.Choices!.First().Delta!.ToolCalls!).ToList(); + } + + // the usage information will be included in the last result + if (result.Usage != null) + { + finalResult.Usage = result.Usage; + } + } + + finalResult.Choices!.First().Message!.Content.Should().BeNullOrEmpty(); + finalResult.Choices!.First().Message!.ToolCalls!.Count.Should().BeGreaterThan(0); + finalResult.Usage!.TotalTokens.Should().BeGreaterThan(0); + finalResult.Choices!.First().Message!.ToolCalls!.First().Function.Name.Should().Be("GetWeather"); + } +} +public class Person +{ + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + [JsonPropertyName("age")] + public int Age { get; set; } + + [JsonPropertyName("email")] + public string Email { get; set; } = string.Empty; +} diff --git a/dotnet/test/AutoGen.OpenAI.Tests/ApprovalTests/OpenAIMessageTests.BasicMessageTest.approved.txt b/dotnet/test/AutoGen.OpenAI.Tests/ApprovalTests/OpenAIMessageTests.BasicMessageTest.approved.txt new file mode 100644 index 00000000000..d17de56e129 --- /dev/null +++ b/dotnet/test/AutoGen.OpenAI.Tests/ApprovalTests/OpenAIMessageTests.BasicMessageTest.approved.txt @@ -0,0 +1,232 @@ +[ + { + "OriginalMessage": "TextMessage(system, You are a helpful AI assistant, )", + "ConvertedMessages": [ + { + "Name": null, + "Role": "system", + "Content": "You are a helpful AI assistant" + } + ] + }, + { + "OriginalMessage": "TextMessage(user, Hello, user)", + "ConvertedMessages": [ + { + "Role": "user", + "Content": "Hello", + "Name": "user", + "MultiModaItem": null + } + ] + }, + { + "OriginalMessage": "TextMessage(assistant, How can I help you?, assistant)", + "ConvertedMessages": [ + { + "Role": "assistant", + "Content": "How can I help you?", + "Name": "assistant", + "TooCall": [], + "FunctionCallName": null, + "FunctionCallArguments": null + } + ] + }, + { + "OriginalMessage": "Message(system, You are a helpful AI assistant, , , )", + "ConvertedMessages": [ + { + "Name": null, + "Role": "system", + "Content": "You are a helpful AI assistant" + } + ] + }, + { + "OriginalMessage": "Message(user, Hello, user, , )", + "ConvertedMessages": [ + { + "Role": "user", + "Content": "Hello", + "Name": "user", + "MultiModaItem": null + } + ] + }, + { + "OriginalMessage": "Message(assistant, How can I help you?, assistant, , )", + "ConvertedMessages": [ + { + "Role": "assistant", + "Content": "How can I help you?", + "Name": null, + "TooCall": [], + "FunctionCallName": null, + "FunctionCallArguments": null + } + ] + }, + { + "OriginalMessage": "Message(function, result, user, , )", + "ConvertedMessages": [ + { + "Role": "user", + "Content": "result", + "Name": "user", + "MultiModaItem": null + } + ] + }, + { + "OriginalMessage": "Message(assistant, , assistant, functionName, functionArguments)", + "ConvertedMessages": [ + { + "Role": "assistant", + "Content": null, + "Name": null, + "TooCall": [], + "FunctionCallName": "functionName", + "FunctionCallArguments": "functionArguments" + } + ] + }, + { + "OriginalMessage": "ImageMessage(user, https://example.com/image.png, user)", + "ConvertedMessages": [ + { + "Role": "user", + "Content": null, + "Name": "user", + "MultiModaItem": [ + { + "Type": "Image", + "ImageUrl": { + "Url": "https://example.com/image.png", + "Detail": null + } + } + ] + } + ] + }, + { + "OriginalMessage": "MultiModalMessage(assistant, user)\n\tTextMessage(user, Hello, user)\n\tImageMessage(user, https://example.com/image.png, user)", + "ConvertedMessages": [ + { + "Role": "user", + "Content": null, + "Name": "user", + "MultiModaItem": [ + { + "Type": "Text", + "Text": "Hello" + }, + { + "Type": "Image", + "ImageUrl": { + "Url": "https://example.com/image.png", + "Detail": null + } + } + ] + } + ] + }, + { + "OriginalMessage": "ToolCallMessage(assistant)\n\tToolCall(test, test, )", + "ConvertedMessages": [ + { + "Role": "assistant", + "Content": "", + "Name": "assistant", + "TooCall": [ + { + "Type": "Function", + "Name": "test", + "Arguments": "test", + "Id": "test" + } + ], + "FunctionCallName": null, + "FunctionCallArguments": null + } + ] + }, + { + "OriginalMessage": "ToolCallResultMessage(user)\n\tToolCall(test, test, result)", + "ConvertedMessages": [ + { + "Role": "tool", + "Content": "result", + "ToolCallId": "test" + } + ] + }, + { + "OriginalMessage": "ToolCallResultMessage(user)\n\tToolCall(result, test, test)\n\tToolCall(result, test, test)", + "ConvertedMessages": [ + { + "Role": "tool", + "Content": "test", + "ToolCallId": "result" + }, + { + "Role": "tool", + "Content": "test", + "ToolCallId": "result" + } + ] + }, + { + "OriginalMessage": "ToolCallMessage(assistant)\n\tToolCall(test, test, )\n\tToolCall(test, test, )", + "ConvertedMessages": [ + { + "Role": "assistant", + "Content": "", + "Name": "assistant", + "TooCall": [ + { + "Type": "Function", + "Name": "test", + "Arguments": "test", + "Id": "test" + }, + { + "Type": "Function", + "Name": "test", + "Arguments": "test", + "Id": "test" + } + ], + "FunctionCallName": null, + "FunctionCallArguments": null + } + ] + }, + { + "OriginalMessage": "AggregateMessage(assistant)\n\tToolCallMessage(assistant)\n\tToolCall(test, test, )\n\tToolCallResultMessage(assistant)\n\tToolCall(test, test, result)", + "ConvertedMessages": [ + { + "Role": "assistant", + "Content": "", + "Name": "assistant", + "TooCall": [ + { + "Type": "Function", + "Name": "test", + "Arguments": "test", + "Id": "test" + } + ], + "FunctionCallName": null, + "FunctionCallArguments": null + }, + { + "Role": "tool", + "Content": "result", + "ToolCallId": "test" + } + ] + } +] \ No newline at end of file diff --git a/dotnet/test/AutoGen.OpenAI.Tests/AutoGen.OpenAI.Tests.csproj b/dotnet/test/AutoGen.OpenAI.Tests/AutoGen.OpenAI.Tests.csproj new file mode 100644 index 00000000000..044975354b8 --- /dev/null +++ b/dotnet/test/AutoGen.OpenAI.Tests/AutoGen.OpenAI.Tests.csproj @@ -0,0 +1,32 @@ +ο»Ώ + + + $(TestTargetFramework) + false + True + + + + + + + + + + + + + + + + + + + + $([System.String]::Copy('%(FileName)').Split('.')[0]) + $(ProjectExt.Replace('proj', '')) + %(ParentFile)%(ParentExtension) + + + + diff --git a/dotnet/test/AutoGen.OpenAI.Tests/GlobalUsing.cs b/dotnet/test/AutoGen.OpenAI.Tests/GlobalUsing.cs new file mode 100644 index 00000000000..d66bf001ed5 --- /dev/null +++ b/dotnet/test/AutoGen.OpenAI.Tests/GlobalUsing.cs @@ -0,0 +1,4 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// GlobalUsing.cs + +global using AutoGen.Core; diff --git a/dotnet/test/AutoGen.OpenAI.Tests/OpenAIChatAgentTest.cs b/dotnet/test/AutoGen.OpenAI.Tests/OpenAIChatAgentTest.cs new file mode 100644 index 00000000000..c504eb06a18 --- /dev/null +++ b/dotnet/test/AutoGen.OpenAI.Tests/OpenAIChatAgentTest.cs @@ -0,0 +1,238 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// OpenAIChatAgentTest.cs + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using AutoGen.OpenAI; +using AutoGen.OpenAI.Extension; +using Azure.AI.OpenAI; +using FluentAssertions; + +namespace AutoGen.Tests; + +public partial class OpenAIChatAgentTest +{ + /// + /// Get the weather for a location. + /// + /// location + /// + [Function] + public async Task GetWeatherAsync(string location) + { + return $"The weather in {location} is sunny."; + } + + [ApiKeyFact("AZURE_OPENAI_API_KEY", "AZURE_OPENAI_ENDPOINT")] + public async Task BasicConversationTestAsync() + { + var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new Exception("Please set AZURE_OPENAI_ENDPOINT environment variable."); + var key = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY") ?? throw new Exception("Please set AZURE_OPENAI_API_KEY environment variable."); + var openaiClient = new OpenAIClient(new Uri(endpoint), new Azure.AzureKeyCredential(key)); + var openAIChatAgent = new OpenAIChatAgent( + openAIClient: openaiClient, + name: "assistant", + modelName: "gpt-35-turbo-16k"); + + // By default, OpenAIChatClient supports the following message types + // - IMessage + var chatMessageContent = MessageEnvelope.Create(new ChatRequestUserMessage("Hello")); + var reply = await openAIChatAgent.SendAsync(chatMessageContent); + + reply.Should().BeOfType>(); + reply.As>().From.Should().Be("assistant"); + reply.As>().Content.Choices.First().Message.Role.Should().Be(ChatRole.Assistant); + reply.As>().Content.Usage.TotalTokens.Should().BeGreaterThan(0); + + // test streaming + var streamingReply = openAIChatAgent.GenerateStreamingReplyAsync(new[] { chatMessageContent }); + + await foreach (var streamingMessage in streamingReply) + { + streamingMessage.Should().BeOfType>(); + streamingMessage.As>().From.Should().Be("assistant"); + } + } + + [ApiKeyFact("AZURE_OPENAI_API_KEY", "AZURE_OPENAI_ENDPOINT")] + public async Task OpenAIChatMessageContentConnectorTestAsync() + { + var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new Exception("Please set AZURE_OPENAI_ENDPOINT environment variable."); + var key = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY") ?? throw new Exception("Please set AZURE_OPENAI_API_KEY environment variable."); + var openaiClient = new OpenAIClient(new Uri(endpoint), new Azure.AzureKeyCredential(key)); + var openAIChatAgent = new OpenAIChatAgent( + openAIClient: openaiClient, + name: "assistant", + modelName: "gpt-35-turbo-16k"); + + MiddlewareStreamingAgent assistant = openAIChatAgent + .RegisterMessageConnector(); + + var messages = new IMessage[] + { + MessageEnvelope.Create(new ChatRequestUserMessage("Hello")), + new TextMessage(Role.Assistant, "Hello", from: "user"), + new MultiModalMessage(Role.Assistant, + [ + new TextMessage(Role.Assistant, "Hello", from: "user"), + ], + from: "user"), + new Message(Role.Assistant, "Hello", from: "user"), // Message type is going to be deprecated, please use TextMessage instead + }; + + foreach (var message in messages) + { + var reply = await assistant.SendAsync(message); + + reply.Should().BeOfType(); + reply.As().From.Should().Be("assistant"); + } + + // test streaming + foreach (var message in messages) + { + var reply = assistant.GenerateStreamingReplyAsync([message]); + + await foreach (var streamingMessage in reply) + { + streamingMessage.Should().BeOfType(); + streamingMessage.As().From.Should().Be("assistant"); + } + } + } + + [ApiKeyFact("AZURE_OPENAI_API_KEY", "AZURE_OPENAI_ENDPOINT")] + public async Task OpenAIChatAgentToolCallTestAsync() + { + var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new Exception("Please set AZURE_OPENAI_ENDPOINT environment variable."); + var key = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY") ?? throw new Exception("Please set AZURE_OPENAI_API_KEY environment variable."); + var openaiClient = new OpenAIClient(new Uri(endpoint), new Azure.AzureKeyCredential(key)); + var openAIChatAgent = new OpenAIChatAgent( + openAIClient: openaiClient, + name: "assistant", + modelName: "gpt-35-turbo-16k"); + + var functionCallMiddleware = new FunctionCallMiddleware( + functions: [this.GetWeatherAsyncFunctionContract]); + MiddlewareStreamingAgent assistant = openAIChatAgent + .RegisterMessageConnector(); + + assistant.StreamingMiddlewares.Count().Should().Be(1); + var functionCallAgent = assistant + .RegisterStreamingMiddleware(functionCallMiddleware); + + var question = "What's the weather in Seattle"; + var messages = new IMessage[] + { + MessageEnvelope.Create(new ChatRequestUserMessage(question)), + new TextMessage(Role.Assistant, question, from: "user"), + new MultiModalMessage(Role.Assistant, + [ + new TextMessage(Role.Assistant, question, from: "user"), + ], + from: "user"), + new Message(Role.Assistant, question, from: "user"), // Message type is going to be deprecated, please use TextMessage instead + }; + + foreach (var message in messages) + { + var reply = await functionCallAgent.SendAsync(message); + + reply.Should().BeOfType(); + reply.As().From.Should().Be("assistant"); + reply.As().ToolCalls.Count().Should().Be(1); + reply.As().ToolCalls.First().FunctionName.Should().Be(this.GetWeatherAsyncFunctionContract.Name); + } + + // test streaming + foreach (var message in messages) + { + var reply = functionCallAgent.GenerateStreamingReplyAsync([message]); + ToolCallMessage? toolCallMessage = null; + await foreach (var streamingMessage in reply) + { + streamingMessage.Should().BeOfType(); + streamingMessage.As().From.Should().Be("assistant"); + if (toolCallMessage is null) + { + toolCallMessage = new ToolCallMessage(streamingMessage.As()); + } + else + { + toolCallMessage.Update(streamingMessage.As()); + } + } + + toolCallMessage.Should().NotBeNull(); + toolCallMessage!.From.Should().Be("assistant"); + toolCallMessage.ToolCalls.Count().Should().Be(1); + toolCallMessage.ToolCalls.First().FunctionName.Should().Be(this.GetWeatherAsyncFunctionContract.Name); + } + } + + [ApiKeyFact("AZURE_OPENAI_API_KEY", "AZURE_OPENAI_ENDPOINT")] + public async Task OpenAIChatAgentToolCallInvokingTestAsync() + { + var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new Exception("Please set AZURE_OPENAI_ENDPOINT environment variable."); + var key = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY") ?? throw new Exception("Please set AZURE_OPENAI_API_KEY environment variable."); + var openaiClient = new OpenAIClient(new Uri(endpoint), new Azure.AzureKeyCredential(key)); + var openAIChatAgent = new OpenAIChatAgent( + openAIClient: openaiClient, + name: "assistant", + modelName: "gpt-35-turbo-16k"); + + var functionCallMiddleware = new FunctionCallMiddleware( + functions: [this.GetWeatherAsyncFunctionContract], + functionMap: new Dictionary>> { { this.GetWeatherAsyncFunctionContract.Name!, this.GetWeatherAsyncWrapper } }); + MiddlewareStreamingAgent assistant = openAIChatAgent + .RegisterMessageConnector(); + + var functionCallAgent = assistant + .RegisterStreamingMiddleware(functionCallMiddleware); + + var question = "What's the weather in Seattle"; + var messages = new IMessage[] + { + MessageEnvelope.Create(new ChatRequestUserMessage(question)), + new TextMessage(Role.Assistant, question, from: "user"), + new MultiModalMessage(Role.Assistant, + [ + new TextMessage(Role.Assistant, question, from: "user"), + ], + from: "user"), + new Message(Role.Assistant, question, from: "user"), // Message type is going to be deprecated, please use TextMessage instead + }; + + foreach (var message in messages) + { + var reply = await functionCallAgent.SendAsync(message); + + reply.Should().BeOfType>(); + reply.From.Should().Be("assistant"); + reply.GetToolCalls()!.Count().Should().Be(1); + reply.GetToolCalls()!.First().FunctionName.Should().Be(this.GetWeatherAsyncFunctionContract.Name); + reply.GetContent()!.ToLower().Should().Contain("seattle"); + } + + // test streaming + foreach (var message in messages) + { + var reply = functionCallAgent.GenerateStreamingReplyAsync([message]); + await foreach (var streamingMessage in reply) + { + if (streamingMessage is not IMessage) + { + streamingMessage.Should().BeOfType(); + streamingMessage.As().From.Should().Be("assistant"); + } + else + { + streamingMessage.Should().BeOfType>(); + streamingMessage.As().GetContent()!.ToLower().Should().Contain("seattle"); + } + } + } + } +} diff --git a/dotnet/test/AutoGen.OpenAI.Tests/OpenAIMessageTests.cs b/dotnet/test/AutoGen.OpenAI.Tests/OpenAIMessageTests.cs new file mode 100644 index 00000000000..a8c1d3f7860 --- /dev/null +++ b/dotnet/test/AutoGen.OpenAI.Tests/OpenAIMessageTests.cs @@ -0,0 +1,612 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// OpenAIMessageTests.cs + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text.Json; +using System.Threading.Tasks; +using ApprovalTests; +using ApprovalTests.Namers; +using ApprovalTests.Reporters; +using AutoGen.OpenAI; +using Azure.AI.OpenAI; +using FluentAssertions; +using Xunit; + +namespace AutoGen.Tests; + +public class OpenAIMessageTests +{ + private readonly JsonSerializerOptions jsonSerializerOptions = new JsonSerializerOptions + { + WriteIndented = true, + IgnoreReadOnlyProperties = false, + }; + + [Fact] + [UseReporter(typeof(DiffReporter))] + [UseApprovalSubdirectory("ApprovalTests")] + public void BasicMessageTest() + { + IMessage[] messages = [ + new TextMessage(Role.System, "You are a helpful AI assistant"), + new TextMessage(Role.User, "Hello", "user"), + new TextMessage(Role.Assistant, "How can I help you?", from: "assistant"), + new Message(Role.System, "You are a helpful AI assistant"), + new Message(Role.User, "Hello", "user"), + new Message(Role.Assistant, "How can I help you?", from: "assistant"), + new Message(Role.Function, "result", "user"), + new Message(Role.Assistant, null, "assistant") + { + FunctionName = "functionName", + FunctionArguments = "functionArguments", + }, + new ImageMessage(Role.User, "https://example.com/image.png", "user"), + new MultiModalMessage(Role.Assistant, + [ + new TextMessage(Role.User, "Hello", "user"), + new ImageMessage(Role.User, "https://example.com/image.png", "user"), + ], "user"), + new ToolCallMessage("test", "test", "assistant"), + new ToolCallResultMessage("result", "test", "test", "user"), + new ToolCallResultMessage( + [ + new ToolCall("result", "test", "test"), + new ToolCall("result", "test", "test"), + ], "user"), + new ToolCallMessage( + [ + new ToolCall("test", "test"), + new ToolCall("test", "test"), + ], "assistant"), + new AggregateMessage( + message1: new ToolCallMessage("test", "test", "assistant"), + message2: new ToolCallResultMessage("result", "test", "test", "assistant"), "assistant"), + ]; + var openaiMessageConnectorMiddleware = new OpenAIChatRequestMessageConnector(); + var agent = new EchoAgent("assistant"); + + var oaiMessages = messages.Select(m => (m, openaiMessageConnectorMiddleware.ProcessIncomingMessages(agent, [m]))); + VerifyOAIMessages(oaiMessages); + } + + [Fact] + public async Task ItProcessUserTextMessageAsync() + { + var middleware = new OpenAIChatRequestMessageConnector(); + var agent = new EchoAgent("assistant") + .RegisterMiddleware(async (msgs, _, innerAgent, _) => + { + var innerMessage = msgs.Last(); + innerMessage!.Should().BeOfType>(); + var chatRequestMessage = (ChatRequestUserMessage)((MessageEnvelope)innerMessage!).Content; + chatRequestMessage.Content.Should().Be("Hello"); + chatRequestMessage.Name.Should().Be("user"); + return await innerAgent.GenerateReplyAsync(msgs); + }) + .RegisterMiddleware(middleware); + + // user message + IMessage message = new TextMessage(Role.User, "Hello", "user"); + await agent.GenerateReplyAsync([message]); + } + + [Fact] + public async Task ItShortcutChatRequestMessageAsync() + { + var middleware = new OpenAIChatRequestMessageConnector(); + var agent = new EchoAgent("assistant") + .RegisterMiddleware(async (msgs, _, innerAgent, _) => + { + var innerMessage = msgs.Last(); + innerMessage!.Should().BeOfType>(); + + var chatRequestMessage = (ChatRequestUserMessage)((MessageEnvelope)innerMessage!).Content; + chatRequestMessage.Content.Should().Be("hello"); + return await innerAgent.GenerateReplyAsync(msgs); + }) + .RegisterMiddleware(middleware); + + // user message + var userMessage = new ChatRequestUserMessage("hello"); + var chatRequestMessage = MessageEnvelope.Create(userMessage); + await agent.GenerateReplyAsync([chatRequestMessage]); + } + + [Fact] + public async Task ItShortcutMessageWhenStrictModelIsFalseAsync() + { + var middleware = new OpenAIChatRequestMessageConnector(); + var agent = new EchoAgent("assistant") + .RegisterMiddleware(async (msgs, _, innerAgent, _) => + { + var innerMessage = msgs.Last(); + innerMessage!.Should().BeOfType>(); + + var chatRequestMessage = ((MessageEnvelope)innerMessage!).Content; + chatRequestMessage.Should().Be("hello"); + return await innerAgent.GenerateReplyAsync(msgs); + }) + .RegisterMiddleware(middleware); + + // user message + var userMessage = "hello"; + var chatRequestMessage = MessageEnvelope.Create(userMessage); + await agent.GenerateReplyAsync([chatRequestMessage]); + } + + [Fact] + public async Task ItThrowExceptionWhenStrictModeIsTrueAsync() + { + var middleware = new OpenAIChatRequestMessageConnector(true); + var agent = new EchoAgent("assistant") + .RegisterMiddleware(middleware); + + // user message + var userMessage = "hello"; + var chatRequestMessage = MessageEnvelope.Create(userMessage); + Func action = async () => await agent.GenerateReplyAsync([chatRequestMessage]); + + await action.Should().ThrowAsync().WithMessage("Invalid message type: MessageEnvelope`1"); + } + + [Fact] + public async Task ItProcessAssistantTextMessageAsync() + { + var middleware = new OpenAIChatRequestMessageConnector(); + var agent = new EchoAgent("assistant") + .RegisterMiddleware(async (msgs, _, innerAgent, _) => + { + var innerMessage = msgs.Last(); + innerMessage!.Should().BeOfType>(); + var chatRequestMessage = (ChatRequestAssistantMessage)((MessageEnvelope)innerMessage!).Content; + chatRequestMessage.Content.Should().Be("How can I help you?"); + chatRequestMessage.Name.Should().Be("assistant"); + return await innerAgent.GenerateReplyAsync(msgs); + }) + .RegisterMiddleware(middleware); + + // assistant message + IMessage message = new TextMessage(Role.Assistant, "How can I help you?", "assistant"); + await agent.GenerateReplyAsync([message]); + } + + [Fact] + public async Task ItProcessSystemTextMessageAsync() + { + var middleware = new OpenAIChatRequestMessageConnector(); + var agent = new EchoAgent("assistant") + .RegisterMiddleware(async (msgs, _, innerAgent, _) => + { + var innerMessage = msgs.Last(); + innerMessage!.Should().BeOfType>(); + var chatRequestMessage = (ChatRequestSystemMessage)((MessageEnvelope)innerMessage!).Content; + chatRequestMessage.Content.Should().Be("You are a helpful AI assistant"); + return await innerAgent.GenerateReplyAsync(msgs); + }) + .RegisterMiddleware(middleware); + + // system message + IMessage message = new TextMessage(Role.System, "You are a helpful AI assistant"); + await agent.GenerateReplyAsync([message]); + } + + [Fact] + public async Task ItProcessImageMessageAsync() + { + var middleware = new OpenAIChatRequestMessageConnector(); + var agent = new EchoAgent("assistant") + .RegisterMiddleware(async (msgs, _, innerAgent, _) => + { + var innerMessage = msgs.Last(); + innerMessage!.Should().BeOfType>(); + var chatRequestMessage = (ChatRequestUserMessage)((MessageEnvelope)innerMessage!).Content; + chatRequestMessage.Content.Should().BeNullOrEmpty(); + chatRequestMessage.Name.Should().Be("user"); + chatRequestMessage.MultimodalContentItems.Count().Should().Be(1); + chatRequestMessage.MultimodalContentItems.First().Should().BeOfType(); + return await innerAgent.GenerateReplyAsync(msgs); + }) + .RegisterMiddleware(middleware); + + // user message + IMessage message = new ImageMessage(Role.User, "https://example.com/image.png", "user"); + await agent.GenerateReplyAsync([message]); + } + + [Fact] + public async Task ItThrowExceptionWhenProcessingImageMessageFromSelfAndStrictModeIsTrueAsync() + { + var middleware = new OpenAIChatRequestMessageConnector(true); + var agent = new EchoAgent("assistant") + .RegisterMiddleware(middleware); + + var imageMessage = new ImageMessage(Role.Assistant, "https://example.com/image.png", "assistant"); + Func action = async () => await agent.GenerateReplyAsync([imageMessage]); + + await action.Should().ThrowAsync().WithMessage("Invalid message type: ImageMessage"); + } + + [Fact] + public async Task ItProcessMultiModalMessageAsync() + { + var middleware = new OpenAIChatRequestMessageConnector(); + var agent = new EchoAgent("assistant") + .RegisterMiddleware(async (msgs, _, innerAgent, _) => + { + var innerMessage = msgs.Last(); + innerMessage!.Should().BeOfType>(); + var chatRequestMessage = (ChatRequestUserMessage)((MessageEnvelope)innerMessage!).Content; + chatRequestMessage.Content.Should().BeNullOrEmpty(); + chatRequestMessage.Name.Should().Be("user"); + chatRequestMessage.MultimodalContentItems.Count().Should().Be(2); + chatRequestMessage.MultimodalContentItems.First().Should().BeOfType(); + chatRequestMessage.MultimodalContentItems.Last().Should().BeOfType(); + return await innerAgent.GenerateReplyAsync(msgs); + }) + .RegisterMiddleware(middleware); + + // user message + IMessage message = new MultiModalMessage( + Role.User, + [ + new TextMessage(Role.User, "Hello", "user"), + new ImageMessage(Role.User, "https://example.com/image.png", "user"), + ], "user"); + await agent.GenerateReplyAsync([message]); + } + + [Fact] + public async Task ItThrowExceptionWhenProcessingMultiModalMessageFromSelfAndStrictModeIsTrueAsync() + { + var middleware = new OpenAIChatRequestMessageConnector(true); + var agent = new EchoAgent("assistant") + .RegisterMiddleware(middleware); + + var multiModalMessage = new MultiModalMessage( + Role.Assistant, + [ + new TextMessage(Role.User, "Hello", "assistant"), + new ImageMessage(Role.User, "https://example.com/image.png", "assistant"), + ], "assistant"); + + Func action = async () => await agent.GenerateReplyAsync([multiModalMessage]); + + await action.Should().ThrowAsync().WithMessage("Invalid message type: MultiModalMessage"); + } + + [Fact] + public async Task ItProcessToolCallMessageAsync() + { + var middleware = new OpenAIChatRequestMessageConnector(); + var agent = new EchoAgent("assistant") + .RegisterMiddleware(async (msgs, _, innerAgent, _) => + { + var innerMessage = msgs.Last(); + innerMessage!.Should().BeOfType>(); + var chatRequestMessage = (ChatRequestAssistantMessage)((MessageEnvelope)innerMessage!).Content; + chatRequestMessage.Content.Should().BeNullOrEmpty(); + chatRequestMessage.Name.Should().Be("assistant"); + chatRequestMessage.ToolCalls.Count().Should().Be(1); + chatRequestMessage.ToolCalls.First().Should().BeOfType(); + var functionToolCall = (ChatCompletionsFunctionToolCall)chatRequestMessage.ToolCalls.First(); + functionToolCall.Name.Should().Be("test"); + functionToolCall.Arguments.Should().Be("test"); + return await innerAgent.GenerateReplyAsync(msgs); + }) + .RegisterMiddleware(middleware); + + // user message + IMessage message = new ToolCallMessage("test", "test", "assistant"); + await agent.GenerateReplyAsync([message]); + } + + [Fact] + public async Task ItThrowExceptionWhenProcessingToolCallMessageFromUserAndStrictModeIsTrueAsync() + { + var middleware = new OpenAIChatRequestMessageConnector(strictMode: true); + var agent = new EchoAgent("assistant") + .RegisterMiddleware(middleware); + + var toolCallMessage = new ToolCallMessage("test", "test", "user"); + Func action = async () => await agent.GenerateReplyAsync([toolCallMessage]); + await action.Should().ThrowAsync().WithMessage("Invalid message type: ToolCallMessage"); + } + + [Fact] + public async Task ItProcessToolCallResultMessageAsync() + { + var middleware = new OpenAIChatRequestMessageConnector(); + var agent = new EchoAgent("assistant") + .RegisterMiddleware(async (msgs, _, innerAgent, _) => + { + var innerMessage = msgs.Last(); + innerMessage!.Should().BeOfType>(); + var chatRequestMessage = (ChatRequestToolMessage)((MessageEnvelope)innerMessage!).Content; + chatRequestMessage.Content.Should().Be("result"); + chatRequestMessage.ToolCallId.Should().Be("test"); + return await innerAgent.GenerateReplyAsync(msgs); + }) + .RegisterMiddleware(middleware); + + // user message + IMessage message = new ToolCallResultMessage("result", "test", "test", "user"); + await agent.GenerateReplyAsync([message]); + } + + [Fact] + public async Task ItProcessFunctionCallMiddlewareMessageFromUserAsync() + { + var middleware = new OpenAIChatRequestMessageConnector(); + var agent = new EchoAgent("assistant") + .RegisterMiddleware(async (msgs, _, innerAgent, _) => + { + msgs.Count().Should().Be(1); + var innerMessage = msgs.Last(); + innerMessage!.Should().BeOfType>(); + var chatRequestMessage = (ChatRequestUserMessage)((MessageEnvelope)innerMessage!).Content; + chatRequestMessage.Content.Should().Be("result"); + chatRequestMessage.Name.Should().Be("user"); + return await innerAgent.GenerateReplyAsync(msgs); + }) + .RegisterMiddleware(middleware); + + // user message + var toolCallMessage = new ToolCallMessage("test", "test", "user"); + var toolCallResultMessage = new ToolCallResultMessage("result", "test", "test", "user"); + var aggregateMessage = new AggregateMessage(toolCallMessage, toolCallResultMessage, "user"); + await agent.GenerateReplyAsync([aggregateMessage]); + } + + [Fact] + public async Task ItProcessFunctionCallMiddlewareMessageFromAssistantAsync() + { + var middleware = new OpenAIChatRequestMessageConnector(); + var agent = new EchoAgent("assistant") + .RegisterMiddleware(async (msgs, _, innerAgent, _) => + { + msgs.Count().Should().Be(2); + var innerMessage = msgs.Last(); + innerMessage!.Should().BeOfType>(); + var chatRequestMessage = (ChatRequestToolMessage)((MessageEnvelope)innerMessage!).Content; + chatRequestMessage.Content.Should().Be("result"); + + var toolCallMessage = msgs.First(); + toolCallMessage!.Should().BeOfType>(); + var toolCallRequestMessage = (ChatRequestAssistantMessage)((MessageEnvelope)toolCallMessage!).Content; + toolCallRequestMessage.Content.Should().BeNullOrEmpty(); + toolCallRequestMessage.ToolCalls.Count().Should().Be(1); + toolCallRequestMessage.ToolCalls.First().Should().BeOfType(); + var functionToolCall = (ChatCompletionsFunctionToolCall)toolCallRequestMessage.ToolCalls.First(); + functionToolCall.Name.Should().Be("test"); + functionToolCall.Arguments.Should().Be("test"); + return await innerAgent.GenerateReplyAsync(msgs); + }) + .RegisterMiddleware(middleware); + + // user message + var toolCallMessage = new ToolCallMessage("test", "test", "assistant"); + var toolCallResultMessage = new ToolCallResultMessage("result", "test", "test", "assistant"); + var aggregateMessage = new AggregateMessage(toolCallMessage, toolCallResultMessage, "assistant"); + await agent.GenerateReplyAsync([aggregateMessage]); + } + + [Fact] + public async Task ItConvertChatResponseMessageToTextMessageAsync() + { + var middleware = new OpenAIChatRequestMessageConnector(); + var agent = new EchoAgent("assistant") + .RegisterMiddleware(middleware); + + // text message + var textMessage = CreateInstance(ChatRole.Assistant, "hello"); + var chatRequestMessage = MessageEnvelope.Create(textMessage); + + var message = await agent.GenerateReplyAsync([chatRequestMessage]); + message.Should().BeOfType(); + message.GetContent().Should().Be("hello"); + message.GetRole().Should().Be(Role.Assistant); + } + + [Fact] + public async Task ItConvertChatResponseMessageToToolCallMessageAsync() + { + var middleware = new OpenAIChatRequestMessageConnector(); + var agent = new EchoAgent("assistant") + .RegisterMiddleware(middleware); + + // tool call message + var toolCallMessage = CreateInstance(ChatRole.Assistant, "", new[] { new ChatCompletionsFunctionToolCall("test", "test", "test") }, new FunctionCall("test", "test"), CreateInstance(), new Dictionary()); + var chatRequestMessage = MessageEnvelope.Create(toolCallMessage); + var message = await agent.GenerateReplyAsync([chatRequestMessage]); + message.Should().BeOfType(); + message.GetToolCalls()!.Count().Should().Be(1); + message.GetToolCalls()!.First().FunctionName.Should().Be("test"); + message.GetToolCalls()!.First().FunctionArguments.Should().Be("test"); + } + + [Fact] + public async Task ItReturnOriginalMessageWhenStrictModeIsFalseAsync() + { + var middleware = new OpenAIChatRequestMessageConnector(); + var agent = new EchoAgent("assistant") + .RegisterMiddleware(middleware); + + // text message + var textMessage = "hello"; + var messageToSend = MessageEnvelope.Create(textMessage); + + var message = await agent.GenerateReplyAsync([messageToSend]); + message.Should().BeOfType>(); + } + + [Fact] + public async Task ItThrowInvalidOperationExceptionWhenStrictModeIsTrueAsync() + { + var middleware = new OpenAIChatRequestMessageConnector(true); + var agent = new EchoAgent("assistant") + .RegisterMiddleware(middleware); + + // text message + var textMessage = new ChatRequestUserMessage("hello"); + var messageToSend = MessageEnvelope.Create(textMessage); + Func action = async () => await agent.GenerateReplyAsync([messageToSend]); + + await action.Should().ThrowAsync().WithMessage("Invalid return message type MessageEnvelope`1"); + } + + [Fact] + public void ToOpenAIChatRequestMessageShortCircuitTest() + { + var agent = new EchoAgent("assistant"); + var middleware = new OpenAIChatRequestMessageConnector(); + ChatRequestMessage[] messages = + [ + new ChatRequestUserMessage("Hello"), + new ChatRequestAssistantMessage("How can I help you?"), + new ChatRequestSystemMessage("You are a helpful AI assistant"), + new ChatRequestFunctionMessage("result", "functionName"), + new ChatRequestToolMessage("test", "test"), + ]; + + foreach (var oaiMessage in messages) + { + IMessage message = new MessageEnvelope(oaiMessage); + var oaiMessages = middleware.ProcessIncomingMessages(agent, [message]); + oaiMessages.Count().Should().Be(1); + //oaiMessages.First().Should().BeOfType>(); + if (oaiMessages.First() is IMessage chatRequestMessage) + { + chatRequestMessage.Content.Should().Be(oaiMessage); + } + else + { + // fail the test + Assert.True(false); + } + } + } + private void VerifyOAIMessages(IEnumerable<(IMessage, IEnumerable)> messages) + { + var jsonObjects = messages.Select(pair => + { + var (originalMessage, ms) = pair; + var objs = new List(); + foreach (var m in ms) + { + object? obj = null; + var chatRequestMessage = (m as IMessage)?.Content; + if (chatRequestMessage is ChatRequestUserMessage userMessage) + { + obj = new + { + Role = userMessage.Role.ToString(), + Content = userMessage.Content, + Name = userMessage.Name, + MultiModaItem = userMessage.MultimodalContentItems?.Select(item => + { + return item switch + { + ChatMessageImageContentItem imageContentItem => new + { + Type = "Image", + ImageUrl = GetImageUrlFromContent(imageContentItem), + } as object, + ChatMessageTextContentItem textContentItem => new + { + Type = "Text", + Text = textContentItem.Text, + } as object, + _ => throw new System.NotImplementedException(), + }; + }), + }; + } + + if (chatRequestMessage is ChatRequestAssistantMessage assistantMessage) + { + obj = new + { + Role = assistantMessage.Role.ToString(), + Content = assistantMessage.Content, + Name = assistantMessage.Name, + TooCall = assistantMessage.ToolCalls.Select(tc => + { + return tc switch + { + ChatCompletionsFunctionToolCall functionToolCall => new + { + Type = "Function", + Name = functionToolCall.Name, + Arguments = functionToolCall.Arguments, + Id = functionToolCall.Id, + } as object, + _ => throw new System.NotImplementedException(), + }; + }), + FunctionCallName = assistantMessage.FunctionCall?.Name, + FunctionCallArguments = assistantMessage.FunctionCall?.Arguments, + }; + } + + if (chatRequestMessage is ChatRequestSystemMessage systemMessage) + { + obj = new + { + Name = systemMessage.Name, + Role = systemMessage.Role.ToString(), + Content = systemMessage.Content, + }; + } + + if (chatRequestMessage is ChatRequestFunctionMessage functionMessage) + { + obj = new + { + Role = functionMessage.Role.ToString(), + Content = functionMessage.Content, + Name = functionMessage.Name, + }; + } + + if (chatRequestMessage is ChatRequestToolMessage toolCallMessage) + { + obj = new + { + Role = toolCallMessage.Role.ToString(), + Content = toolCallMessage.Content, + ToolCallId = toolCallMessage.ToolCallId, + }; + } + + objs.Add(obj ?? throw new System.NotImplementedException()); + } + + return new + { + OriginalMessage = originalMessage.ToString(), + ConvertedMessages = objs, + }; + }); + + var json = JsonSerializer.Serialize(jsonObjects, this.jsonSerializerOptions); + Approvals.Verify(json); + } + + private object? GetImageUrlFromContent(ChatMessageImageContentItem content) + { + return content.GetType().GetProperty("ImageUrl", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)?.GetValue(content); + } + + private static T CreateInstance(params object[] args) + { + var type = typeof(T); + var instance = type.Assembly.CreateInstance( + type.FullName!, false, + BindingFlags.Instance | BindingFlags.NonPublic, + null, args, null, null); + return (T)instance!; + } +} diff --git a/dotnet/test/AutoGen.SemanticKernel.Tests/ApprovalTests/KernelFunctionExtensionTests.ItCreateFunctionContractsFromMethod.approved.txt b/dotnet/test/AutoGen.SemanticKernel.Tests/ApprovalTests/KernelFunctionExtensionTests.ItCreateFunctionContractsFromMethod.approved.txt new file mode 100644 index 00000000000..677831d412b --- /dev/null +++ b/dotnet/test/AutoGen.SemanticKernel.Tests/ApprovalTests/KernelFunctionExtensionTests.ItCreateFunctionContractsFromMethod.approved.txt @@ -0,0 +1,24 @@ +ο»Ώ[ + { + "Name": "_ItCreateFunctionContractsFromMethod_b__2_0", + "Description": "", + "Parameters": [], + "ReturnType": "System.String, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e", + "ReturnDescription": "" + }, + { + "Name": "_ItCreateFunctionContractsFromMethod_b__2_1", + "Description": "", + "Parameters": [ + { + "Name": "message", + "Description": "", + "ParameterType": "System.String, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e", + "IsRequired": true, + "DefaultValue": "" + } + ], + "ReturnType": "System.String, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e", + "ReturnDescription": "" + } +] \ No newline at end of file diff --git a/dotnet/test/AutoGen.SemanticKernel.Tests/ApprovalTests/KernelFunctionExtensionTests.ItCreateFunctionContractsFromPrompt.approved.txt b/dotnet/test/AutoGen.SemanticKernel.Tests/ApprovalTests/KernelFunctionExtensionTests.ItCreateFunctionContractsFromPrompt.approved.txt new file mode 100644 index 00000000000..428f53572f1 --- /dev/null +++ b/dotnet/test/AutoGen.SemanticKernel.Tests/ApprovalTests/KernelFunctionExtensionTests.ItCreateFunctionContractsFromPrompt.approved.txt @@ -0,0 +1,8 @@ +ο»Ώ[ + { + "Name": "sayHello", + "Description": "Generic function, unknown purpose", + "Parameters": [], + "ReturnDescription": "" + } +] \ No newline at end of file diff --git a/dotnet/test/AutoGen.SemanticKernel.Tests/ApprovalTests/KernelFunctionExtensionTests.ItCreateFunctionContractsFromTestPlugin.approved.txt b/dotnet/test/AutoGen.SemanticKernel.Tests/ApprovalTests/KernelFunctionExtensionTests.ItCreateFunctionContractsFromTestPlugin.approved.txt new file mode 100644 index 00000000000..ee835b1ba08 --- /dev/null +++ b/dotnet/test/AutoGen.SemanticKernel.Tests/ApprovalTests/KernelFunctionExtensionTests.ItCreateFunctionContractsFromTestPlugin.approved.txt @@ -0,0 +1,26 @@ +ο»Ώ[ + { + "ClassName": "test_plugin", + "Name": "GetState", + "Description": "Gets the state of the light.", + "Parameters": [], + "ReturnType": "System.String, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e", + "ReturnDescription": "" + }, + { + "ClassName": "test_plugin", + "Name": "ChangeState", + "Description": "Changes the state of the light.'", + "Parameters": [ + { + "Name": "newState", + "Description": "new state", + "ParameterType": "System.Boolean, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e", + "IsRequired": true, + "DefaultValue": "" + } + ], + "ReturnType": "System.String, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e", + "ReturnDescription": "" + } +] \ No newline at end of file diff --git a/dotnet/test/AutoGen.SemanticKernel.Tests/AutoGen.SemanticKernel.Tests.csproj b/dotnet/test/AutoGen.SemanticKernel.Tests/AutoGen.SemanticKernel.Tests.csproj new file mode 100644 index 00000000000..b6d03ddc4af --- /dev/null +++ b/dotnet/test/AutoGen.SemanticKernel.Tests/AutoGen.SemanticKernel.Tests.csproj @@ -0,0 +1,27 @@ +ο»Ώ + + + $(TestTargetFramework) + enable + false + $(NoWarn);SKEXP0110 + True + + + + + + + + + + + + + + + + + + + diff --git a/dotnet/test/AutoGen.SemanticKernel.Tests/KernelFunctionExtensionTests.cs b/dotnet/test/AutoGen.SemanticKernel.Tests/KernelFunctionExtensionTests.cs new file mode 100644 index 00000000000..c898c98b3c0 --- /dev/null +++ b/dotnet/test/AutoGen.SemanticKernel.Tests/KernelFunctionExtensionTests.cs @@ -0,0 +1,104 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// KernelFunctionExtensionTests.cs + +using System.ComponentModel; +using ApprovalTests; +using ApprovalTests.Namers; +using ApprovalTests.Reporters; +using AutoGen.SemanticKernel.Extension; +using FluentAssertions; +using Microsoft.SemanticKernel; +using Newtonsoft.Json; +using Xunit; + +namespace AutoGen.SemanticKernel.Tests; + +public class TestPlugin +{ + public bool IsOn { get; set; } = false; + + [KernelFunction] + [Description("Gets the state of the light.")] + public string GetState() => this.IsOn ? "on" : "off"; + + [KernelFunction] + [Description("Changes the state of the light.'")] + public string ChangeState( + [Description("new state")] bool newState) + { + this.IsOn = newState; + var state = this.GetState(); + + // Print the state to the console + Console.ForegroundColor = ConsoleColor.DarkBlue; + Console.WriteLine($"[Light is now {state}]"); + Console.ResetColor(); + + return $"The status of the light is now {state}"; + } +} +public class KernelFunctionExtensionTests +{ + private readonly JsonSerializerSettings _serializerSettings = new JsonSerializerSettings + { + Formatting = Formatting.Indented, + NullValueHandling = NullValueHandling.Ignore, + StringEscapeHandling = StringEscapeHandling.Default, + }; + + [Fact] + [UseReporter(typeof(DiffReporter))] + [UseApprovalSubdirectory("ApprovalTests")] + public void ItCreateFunctionContractsFromTestPlugin() + { + var kernel = new Kernel(); + var plugin = kernel.ImportPluginFromType("test_plugin"); + + var functionContracts = plugin.Select(f => f.Metadata.ToFunctionContract()).ToList(); + + functionContracts.Count.Should().Be(2); + var json = JsonConvert.SerializeObject(functionContracts, _serializerSettings); + + Approvals.Verify(json); + } + + [Fact] + [UseReporter(typeof(DiffReporter))] + [UseApprovalSubdirectory("ApprovalTests")] + public void ItCreateFunctionContractsFromMethod() + { + var kernel = new Kernel(); + var sayHelloFunction = KernelFunctionFactory.CreateFromMethod(() => "Hello, World!"); + var echoFunction = KernelFunctionFactory.CreateFromMethod((string message) => message); + + var functionContracts = new[] + { + sayHelloFunction.Metadata.ToFunctionContract(), + echoFunction.Metadata.ToFunctionContract(), + }; + + var json = JsonConvert.SerializeObject(functionContracts, _serializerSettings); + + functionContracts.Length.Should().Be(2); + Approvals.Verify(json); + } + + [Fact] + [UseReporter(typeof(DiffReporter))] + [UseApprovalSubdirectory("ApprovalTests")] + public void ItCreateFunctionContractsFromPrompt() + { + var kernel = new Kernel(); + var sayHelloFunction = KernelFunctionFactory.CreateFromPrompt("Say {{hello}}, World!", functionName: "sayHello"); + + var functionContracts = new[] + { + sayHelloFunction.Metadata.ToFunctionContract(), + }; + + var json = JsonConvert.SerializeObject(functionContracts, _serializerSettings); + + functionContracts.Length.Should().Be(1); + Approvals.Verify(json); + } +} diff --git a/dotnet/test/AutoGen.SemanticKernel.Tests/KernelFunctionMiddlewareTests.cs b/dotnet/test/AutoGen.SemanticKernel.Tests/KernelFunctionMiddlewareTests.cs new file mode 100644 index 00000000000..f560419e8c8 --- /dev/null +++ b/dotnet/test/AutoGen.SemanticKernel.Tests/KernelFunctionMiddlewareTests.cs @@ -0,0 +1,121 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// KernelFunctionMiddlewareTests.cs + +using AutoGen.Core; +using AutoGen.OpenAI; +using AutoGen.OpenAI.Extension; +using AutoGen.Tests; +using Azure.AI.OpenAI; +using FluentAssertions; +using Microsoft.SemanticKernel; + +namespace AutoGen.SemanticKernel.Tests; + +public class KernelFunctionMiddlewareTests +{ + [ApiKeyFact("AZURE_OPENAI_API_KEY", "AZURE_OPENAI_ENDPOINT")] + public async Task ItRegisterKernelFunctionMiddlewareFromTestPluginTests() + { + var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new Exception("Please set AZURE_OPENAI_ENDPOINT environment variable."); + var key = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY") ?? throw new Exception("Please set AZURE_OPENAI_API_KEY environment variable."); + var openaiClient = new OpenAIClient(new Uri(endpoint), new Azure.AzureKeyCredential(key)); + + var kernel = new Kernel(); + var plugin = kernel.ImportPluginFromType(); + var kernelFunctionMiddleware = new KernelPluginMiddleware(kernel, plugin); + + var agent = new OpenAIChatAgent(openaiClient, "assistant", modelName: "gpt-35-turbo-16k") + .RegisterMessageConnector() + .RegisterMiddleware(kernelFunctionMiddleware); + + var reply = await agent.SendAsync("what's the status of the light?"); + reply.GetContent().Should().Be("off"); + reply.Should().BeOfType>(); + if (reply is AggregateMessage aggregateMessage) + { + var toolCallMessage = aggregateMessage.Message1; + toolCallMessage.ToolCalls.Should().HaveCount(1); + toolCallMessage.ToolCalls[0].FunctionName.Should().Be("GetState"); + + var toolCallResultMessage = aggregateMessage.Message2; + toolCallResultMessage.ToolCalls.Should().HaveCount(1); + toolCallResultMessage.ToolCalls[0].Result.Should().Be("off"); + } + + reply = await agent.SendAsync("change the status of the light to on"); + reply.GetContent().Should().Be("The status of the light is now on"); + reply.Should().BeOfType>(); + if (reply is AggregateMessage aggregateMessage1) + { + var toolCallMessage = aggregateMessage1.Message1; + toolCallMessage.ToolCalls.Should().HaveCount(1); + toolCallMessage.ToolCalls[0].FunctionName.Should().Be("ChangeState"); + + var toolCallResultMessage = aggregateMessage1.Message2; + toolCallResultMessage.ToolCalls.Should().HaveCount(1); + } + } + + [ApiKeyFact("AZURE_OPENAI_API_KEY", "AZURE_OPENAI_ENDPOINT")] + public async Task ItRegisterKernelFunctionMiddlewareFromMethodTests() + { + var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new Exception("Please set AZURE_OPENAI_ENDPOINT environment variable."); + var key = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY") ?? throw new Exception("Please set AZURE_OPENAI_API_KEY environment variable."); + var openaiClient = new OpenAIClient(new Uri(endpoint), new Azure.AzureKeyCredential(key)); + + var kernel = new Kernel(); + var getWeatherMethod = kernel.CreateFunctionFromMethod((string location) => $"The weather in {location} is sunny.", functionName: "GetWeather", description: "Get the weather for a location."); + var createPersonObjectMethod = kernel.CreateFunctionFromMethod((string name, string email, int age) => new Person(name, email, age), functionName: "CreatePersonObject", description: "Creates a person object."); + var plugin = kernel.ImportPluginFromFunctions("plugin", [getWeatherMethod, createPersonObjectMethod]); + var kernelFunctionMiddleware = new KernelPluginMiddleware(kernel, plugin); + + var agent = new OpenAIChatAgent(openaiClient, "assistant", modelName: "gpt-35-turbo-16k") + .RegisterMessageConnector() + .RegisterMiddleware(kernelFunctionMiddleware); + + var reply = await agent.SendAsync("what's the weather in Seattle?"); + reply.GetContent().Should().Be("The weather in Seattle is sunny."); + reply.Should().BeOfType>(); + if (reply is AggregateMessage getWeatherMessage) + { + var toolCallMessage = getWeatherMessage.Message1; + toolCallMessage.ToolCalls.Should().HaveCount(1); + toolCallMessage.ToolCalls[0].FunctionName.Should().Be("GetWeather"); + + var toolCallResultMessage = getWeatherMessage.Message2; + toolCallResultMessage.ToolCalls.Should().HaveCount(1); + } + + reply = await agent.SendAsync("Create a person object with name: John, email: 12345@gmail.com, age: 30"); + reply.GetContent().Should().Be("Name: John, Email: 12345@gmail.com, Age: 30"); + reply.Should().BeOfType>(); + if (reply is AggregateMessage createPersonObjectMessage) + { + var toolCallMessage = createPersonObjectMessage.Message1; + toolCallMessage.ToolCalls.Should().HaveCount(1); + toolCallMessage.ToolCalls[0].FunctionName.Should().Be("CreatePersonObject"); + + var toolCallResultMessage = createPersonObjectMessage.Message2; + toolCallResultMessage.ToolCalls.Should().HaveCount(1); + } + } +} + +public class Person +{ + public Person(string name, string email, int age) + { + this.Name = name; + this.Email = email; + this.Age = age; + } + + public string Name { get; set; } + public string Email { get; set; } + public int Age { get; set; } + + public override string ToString() + { + return $"Name: {this.Name}, Email: {this.Email}, Age: {this.Age}"; + } +} diff --git a/dotnet/test/AutoGen.SemanticKernel.Tests/SemanticKernelAgentTest.cs b/dotnet/test/AutoGen.SemanticKernel.Tests/SemanticKernelAgentTest.cs new file mode 100644 index 00000000000..14c27cb48a7 --- /dev/null +++ b/dotnet/test/AutoGen.SemanticKernel.Tests/SemanticKernelAgentTest.cs @@ -0,0 +1,236 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// SemanticKernelAgentTest.cs + +using AutoGen.Core; +using AutoGen.SemanticKernel.Extension; +using AutoGen.Tests; +using FluentAssertions; +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.Agents; +using Microsoft.SemanticKernel.ChatCompletion; +using Microsoft.SemanticKernel.Connectors.OpenAI; + +namespace AutoGen.SemanticKernel.Tests; + +public partial class SemanticKernelAgentTest +{ + /// + /// Get the weather for a location. + /// + /// location + /// + [Function] + public async Task GetWeatherAsync(string location) + { + return $"The weather in {location} is sunny."; + } + + [ApiKeyFact("AZURE_OPENAI_API_KEY", "AZURE_OPENAI_ENDPOINT")] + public async Task BasicConversationTestAsync() + { + var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new Exception("Please set AZURE_OPENAI_ENDPOINT environment variable."); + var key = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY") ?? throw new Exception("Please set AZURE_OPENAI_API_KEY environment variable."); + var builder = Kernel.CreateBuilder() + .AddAzureOpenAIChatCompletion("gpt-35-turbo-16k", endpoint, key); + + var kernel = builder.Build(); + + var skAgent = new SemanticKernelAgent(kernel, "assistant"); + + var chatMessageContent = MessageEnvelope.Create(new ChatMessageContent(AuthorRole.Assistant, "Hello")); + var reply = await skAgent.SendAsync(chatMessageContent); + + reply.Should().BeOfType>(); + reply.As>().From.Should().Be("assistant"); + + // test streaming + var streamingReply = skAgent.GenerateStreamingReplyAsync(new[] { chatMessageContent }); + + await foreach (var streamingMessage in streamingReply) + { + streamingMessage.Should().BeOfType>(); + streamingMessage.As>().From.Should().Be("assistant"); + } + } + + [ApiKeyFact("AZURE_OPENAI_API_KEY", "AZURE_OPENAI_ENDPOINT")] + public async Task SemanticKernelChatMessageContentConnectorTestAsync() + { + var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new Exception("Please set AZURE_OPENAI_ENDPOINT environment variable."); + var key = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY") ?? throw new Exception("Please set AZURE_OPENAI_API_KEY environment variable."); + var builder = Kernel.CreateBuilder() + .AddAzureOpenAIChatCompletion("gpt-35-turbo-16k", endpoint, key); + + var kernel = builder.Build(); + + var skAgent = new SemanticKernelAgent(kernel, "assistant") + .RegisterMessageConnector(); + + var messages = new IMessage[] + { + MessageEnvelope.Create(new ChatMessageContent(AuthorRole.Assistant, "Hello")), + new TextMessage(Role.Assistant, "Hello", from: "user"), new MultiModalMessage(Role.Assistant, + [ + new TextMessage(Role.Assistant, "Hello", from: "user"), + ], + from: "user"), + }; + + foreach (var message in messages) + { + var reply = await skAgent.SendAsync(message); + + reply.Should().BeOfType(); + reply.As().From.Should().Be("assistant"); + } + + // test streaming + foreach (var message in messages) + { + var reply = skAgent.GenerateStreamingReplyAsync([message]); + + await foreach (var streamingMessage in reply) + { + streamingMessage.Should().BeOfType(); + streamingMessage.As().From.Should().Be("assistant"); + } + } + } + + [ApiKeyFact("AZURE_OPENAI_API_KEY", "AZURE_OPENAI_ENDPOINT")] + public async Task SemanticKernelPluginTestAsync() + { + var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new Exception("Please set AZURE_OPENAI_ENDPOINT environment variable."); + var key = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY") ?? throw new Exception("Please set AZURE_OPENAI_API_KEY environment variable."); + var builder = Kernel.CreateBuilder() + .AddAzureOpenAIChatCompletion("gpt-35-turbo-16k", endpoint, key); + + var parameters = this.GetWeatherAsyncFunctionContract.Parameters!.Select(p => new KernelParameterMetadata(p.Name!) + { + Description = p.Description, + DefaultValue = p.DefaultValue, + IsRequired = p.IsRequired, + ParameterType = p.ParameterType, + }); + var function = KernelFunctionFactory.CreateFromMethod(this.GetWeatherAsync, this.GetWeatherAsyncFunctionContract.Name, this.GetWeatherAsyncFunctionContract.Description, parameters); + builder.Plugins.AddFromFunctions("plugins", [function]); + var kernel = builder.Build(); + + var skAgent = new SemanticKernelAgent(kernel, "assistant") + .RegisterMessageConnector(); + + skAgent.StreamingMiddlewares.Count().Should().Be(1); + + var question = "What is the weather in Seattle?"; + var reply = await skAgent.SendAsync(question); + + reply.GetContent()!.ToLower().Should().Contain("seattle"); + reply.GetContent()!.ToLower().Should().Contain("sunny"); + } + + + [ApiKeyFact("AZURE_OPENAI_API_KEY", "AZURE_OPENAI_ENDPOINT")] + public async Task BasicSkChatCompletionAgentConversationTestAsync() + { + var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new Exception("Please set AZURE_OPENAI_ENDPOINT environment variable."); + var key = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY") ?? throw new Exception("Please set AZURE_OPENAI_API_KEY environment variable."); + var builder = Kernel.CreateBuilder() + .AddAzureOpenAIChatCompletion("gpt-35-turbo-16k", endpoint, key); + + var kernel = builder.Build(); + var agent = new ChatCompletionAgent() + { + Kernel = kernel, + Name = "assistant", + Instructions = "You are a helpful AI assistant" + }; + + var skAgent = new SemanticKernelChatCompletionAgent(agent); + + var chatMessageContent = MessageEnvelope.Create(new ChatMessageContent(AuthorRole.Assistant, "Hello")); + var reply = await skAgent.SendAsync(chatMessageContent); + + reply.Should().BeOfType>(); + reply.As>().From.Should().Be("assistant"); + } + + [ApiKeyFact("AZURE_OPENAI_API_KEY", "AZURE_OPENAI_ENDPOINT")] + public async Task SkChatCompletionAgentChatMessageContentConnectorTestAsync() + { + var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new Exception("Please set AZURE_OPENAI_ENDPOINT environment variable."); + var key = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY") ?? throw new Exception("Please set AZURE_OPENAI_API_KEY environment variable."); + var builder = Kernel.CreateBuilder() + .AddAzureOpenAIChatCompletion("gpt-35-turbo-16k", endpoint, key); + + var kernel = builder.Build(); + + var connector = new SemanticKernelChatMessageContentConnector(); + var agent = new ChatCompletionAgent() + { + Kernel = kernel, + Name = "assistant", + Instructions = "You are a helpful AI assistant" + }; + var skAgent = new SemanticKernelChatCompletionAgent(agent) + .RegisterMiddleware(connector); + + var messages = new IMessage[] + { + MessageEnvelope.Create(new ChatMessageContent(AuthorRole.Assistant, "Hello")), + new TextMessage(Role.Assistant, "Hello", from: "user"), new MultiModalMessage(Role.Assistant, + [ + new TextMessage(Role.Assistant, "Hello", from: "user"), + ], + from: "user"), + }; + + foreach (var message in messages) + { + var reply = await skAgent.SendAsync(message); + + reply.Should().BeOfType(); + reply.As().From.Should().Be("assistant"); + } + } + + [ApiKeyFact("AZURE_OPENAI_API_KEY", "AZURE_OPENAI_ENDPOINT")] + public async Task SkChatCompletionAgentPluginTestAsync() + { + var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new Exception("Please set AZURE_OPENAI_ENDPOINT environment variable."); + var key = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY") ?? throw new Exception("Please set AZURE_OPENAI_API_KEY environment variable."); + var builder = Kernel.CreateBuilder() + .AddAzureOpenAIChatCompletion("gpt-35-turbo-16k", endpoint, key); + + var parameters = this.GetWeatherAsyncFunctionContract.Parameters!.Select(p => new KernelParameterMetadata(p.Name!) + { + Description = p.Description, + DefaultValue = p.DefaultValue, + IsRequired = p.IsRequired, + ParameterType = p.ParameterType, + }); + var function = KernelFunctionFactory.CreateFromMethod(this.GetWeatherAsync, this.GetWeatherAsyncFunctionContract.Name, this.GetWeatherAsyncFunctionContract.Description, parameters); + builder.Plugins.AddFromFunctions("plugins", [function]); + var kernel = builder.Build(); + + var agent = new ChatCompletionAgent() + { + Kernel = kernel, + Name = "assistant", + Instructions = "You are a helpful AI assistant", + ExecutionSettings = + new OpenAIPromptExecutionSettings() + { + ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions + } + }; + var skAgent = + new SemanticKernelChatCompletionAgent(agent).RegisterMiddleware( + new SemanticKernelChatMessageContentConnector()); + + var question = "What is the weather in Seattle?"; + var reply = await skAgent.SendAsync(question); + + reply.GetContent()!.ToLower().Should().Contain("seattle"); + reply.GetContent()!.ToLower().Should().Contain("sunny"); + } +} diff --git a/dotnet/test/AutoGen.SourceGenerator.Tests/ApprovalTests/FunctionCallTemplateTests.TestFunctionCallTemplate.approved.txt b/dotnet/test/AutoGen.SourceGenerator.Tests/ApprovalTests/FunctionCallTemplateTests.TestFunctionCallTemplate.approved.txt new file mode 100644 index 00000000000..feab4ebd607 --- /dev/null +++ b/dotnet/test/AutoGen.SourceGenerator.Tests/ApprovalTests/FunctionCallTemplateTests.TestFunctionCallTemplate.approved.txt @@ -0,0 +1,71 @@ +ο»Ώο»Ώ//---------------------- +// +// This code was generated by a tool. +// +//---------------------- +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Threading.Tasks; +using System; +using AutoGen.Core; +using AutoGen.OpenAI.Extension; + +namespace AutoGen.SourceGenerator.Tests +{ + public partial class FunctionExamples + { + + private class AddAsyncSchema + { + [JsonPropertyName(@"a")] + public System.Int32 a {get; set;} + [JsonPropertyName(@"b")] + public System.Int32 b {get; set;} + } + + public System.Threading.Tasks.Task`1[System.String] AddAsyncWrapper(string arguments) + { + var schema = JsonSerializer.Deserialize( + arguments, + new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + }); + + return AddAsync(schema.a, schema.b); + } + + public FunctionContract AddAsyncFunctionContract + { + get => new FunctionContract + { + Name = @"AddAsync", + Description = @"Add two numbers.", + ReturnType = typeof(System.Threading.Tasks.Task`1[System.String]), + Parameters = new [] + { + new FunctionParameterContract + { + Name = @"a", + Description = @"The first number.", + ParameterType = typeof(System.Int32), + IsRequired = true, + }, + new FunctionParameterContract + { + Name = @"b", + Description = @"The second number.", + ParameterType = typeof(System.Int32), + IsRequired = true, + }, + }, + }; + } + + public global::Azure.AI.OpenAI.FunctionDefinition AddAsyncFunction + { + get => this.AddAsyncFunctionContract.ToOpenAIFunctionDefinition(); + } + } +} + diff --git a/dotnet/test/AutoGen.SourceGenerator.Tests/ApprovalTests/FunctionExample.Add_Test.approved.txt b/dotnet/test/AutoGen.SourceGenerator.Tests/ApprovalTests/FunctionExample.Add_Test.approved.txt new file mode 100644 index 00000000000..9075d35b957 --- /dev/null +++ b/dotnet/test/AutoGen.SourceGenerator.Tests/ApprovalTests/FunctionExample.Add_Test.approved.txt @@ -0,0 +1,21 @@ +{ + "name": "Add", + "description": "Add function", + "parameters": { + "type": "object", + "properties": { + "a": { + "type": "integer", + "description": "a" + }, + "b": { + "type": "integer", + "description": "b" + } + }, + "required": [ + "a", + "b" + ] + } +} \ No newline at end of file diff --git a/dotnet/test/AutoGen.SourceGenerator.Tests/ApprovalTests/FunctionExample.DictionaryToString_Test.approved.txt b/dotnet/test/AutoGen.SourceGenerator.Tests/ApprovalTests/FunctionExample.DictionaryToString_Test.approved.txt new file mode 100644 index 00000000000..8b6aad2fcda --- /dev/null +++ b/dotnet/test/AutoGen.SourceGenerator.Tests/ApprovalTests/FunctionExample.DictionaryToString_Test.approved.txt @@ -0,0 +1,19 @@ +{ + "name": "DictionaryToStringAsync", + "description": "DictionaryToString function", + "parameters": { + "type": "object", + "properties": { + "xargs": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "an object of key-value pairs. key is string, value is string" + } + }, + "required": [ + "xargs" + ] + } +} \ No newline at end of file diff --git a/dotnet/test/AutoGen.SourceGenerator.Tests/ApprovalTests/FunctionExample.Query_Test.approved.txt b/dotnet/test/AutoGen.SourceGenerator.Tests/ApprovalTests/FunctionExample.Query_Test.approved.txt new file mode 100644 index 00000000000..6d16b5a91c0 --- /dev/null +++ b/dotnet/test/AutoGen.SourceGenerator.Tests/ApprovalTests/FunctionExample.Query_Test.approved.txt @@ -0,0 +1,24 @@ +{ + "name": "Query", + "description": "query function", + "parameters": { + "type": "object", + "properties": { + "query": { + "type": "string", + "description": "query, required" + }, + "k": { + "type": "integer", + "description": "top k, optional, default value is 3" + }, + "thresold": { + "type": "number", + "description": "thresold, optional, default value is 0.5" + } + }, + "required": [ + "query" + ] + } +} \ No newline at end of file diff --git a/dotnet/test/AutoGen.SourceGenerator.Tests/ApprovalTests/FunctionExample.Sum_Test.approved.txt b/dotnet/test/AutoGen.SourceGenerator.Tests/ApprovalTests/FunctionExample.Sum_Test.approved.txt new file mode 100644 index 00000000000..ce86faf6a64 --- /dev/null +++ b/dotnet/test/AutoGen.SourceGenerator.Tests/ApprovalTests/FunctionExample.Sum_Test.approved.txt @@ -0,0 +1,19 @@ +{ + "name": "Sum", + "description": "Sum function", + "parameters": { + "type": "object", + "properties": { + "args": { + "type": "array", + "items": { + "type": "number" + }, + "description": "an array of double values" + } + }, + "required": [ + "args" + ] + } +} \ No newline at end of file diff --git a/dotnet/test/AutoGen.SourceGenerator.Tests/AutoGen.SourceGenerator.Tests.csproj b/dotnet/test/AutoGen.SourceGenerator.Tests/AutoGen.SourceGenerator.Tests.csproj new file mode 100644 index 00000000000..0d0d91e0522 --- /dev/null +++ b/dotnet/test/AutoGen.SourceGenerator.Tests/AutoGen.SourceGenerator.Tests.csproj @@ -0,0 +1,24 @@ +ο»Ώ + + + $(TestTargetFramework) + enable + false + True + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dotnet/test/AutoGen.SourceGenerator.Tests/FilescopeNamespaceFunctionExample.cs b/dotnet/test/AutoGen.SourceGenerator.Tests/FilescopeNamespaceFunctionExample.cs new file mode 100644 index 00000000000..8293b26c162 --- /dev/null +++ b/dotnet/test/AutoGen.SourceGenerator.Tests/FilescopeNamespaceFunctionExample.cs @@ -0,0 +1,14 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// FilescopeNamespaceFunctionExample.cs + +using AutoGen.Core; + +namespace AutoGen.SourceGenerator.Tests; +public partial class FilescopeNamespaceFunctionExample +{ + [Function] + public Task Add(int a, int b) + { + return Task.FromResult($"{a + b}"); + } +} diff --git a/dotnet/test/AutoGen.SourceGenerator.Tests/FunctionCallTemplateTests.cs b/dotnet/test/AutoGen.SourceGenerator.Tests/FunctionCallTemplateTests.cs new file mode 100644 index 00000000000..3c1e6c8ede3 --- /dev/null +++ b/dotnet/test/AutoGen.SourceGenerator.Tests/FunctionCallTemplateTests.cs @@ -0,0 +1,46 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// FunctionCallTemplateTests.cs + +using ApprovalTests; +using ApprovalTests.Namers; +using ApprovalTests.Reporters; +using AutoGen.SourceGenerator.Template; +using Xunit; + +namespace AutoGen.SourceGenerator.Tests; + +public class FunctionCallTemplateTests +{ + [Fact] + [UseReporter(typeof(DiffReporter))] + [UseApprovalSubdirectory("ApprovalTests")] + public void TestFunctionCallTemplate() + { + var functionExample = new FunctionExamples(); + var function = functionExample.AddAsyncFunctionContract; + var functionCallTemplate = new FunctionCallTemplate() + { + ClassName = function.ClassName, + NameSpace = function.Namespace, + FunctionContracts = [new SourceGeneratorFunctionContract() + { + Name = function.Name, + Description = function.Description, + ReturnType = function.ReturnType!.ToString(), + ReturnDescription = function.ReturnDescription, + Parameters = function.Parameters!.Select(p => new SourceGeneratorParameterContract() + { + Name = p.Name, + Description = p.Description, + Type = p.ParameterType!.ToString(), + IsOptional = !p.IsRequired, + JsonType = p.ParameterType!.ToString(), + }).ToArray() + }] + }; + + var actual = functionCallTemplate.TransformText(); + + Approvals.Verify(actual); + } +} diff --git a/dotnet/test/AutoGen.SourceGenerator.Tests/FunctionExample.test.cs b/dotnet/test/AutoGen.SourceGenerator.Tests/FunctionExample.test.cs new file mode 100644 index 00000000000..f7b90e0b96f --- /dev/null +++ b/dotnet/test/AutoGen.SourceGenerator.Tests/FunctionExample.test.cs @@ -0,0 +1,130 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// FunctionExample.test.cs + +using System.Text.Json; +using ApprovalTests; +using ApprovalTests.Namers; +using ApprovalTests.Reporters; +using Azure.AI.OpenAI; +using FluentAssertions; +using Xunit; + +namespace AutoGen.SourceGenerator.Tests +{ + public class FunctionExample + { + private readonly FunctionExamples functionExamples = new FunctionExamples(); + private readonly JsonSerializerOptions jsonSerializerOptions = new JsonSerializerOptions + { + WriteIndented = true, + }; + + [Fact] + public void Add_Test() + { + var args = new + { + a = 1, + b = 2, + }; + + this.VerifyFunction(functionExamples.AddWrapper, args, 3); + this.VerifyFunctionDefinition(functionExamples.AddFunction); + } + + [Fact] + public void Sum_Test() + { + var args = new + { + args = new double[] { 1, 2, 3 }, + }; + + this.VerifyFunction(functionExamples.SumWrapper, args, 6.0); + this.VerifyFunctionDefinition(functionExamples.SumFunction); + } + + [Fact] + public async Task DictionaryToString_Test() + { + var args = new + { + xargs = new Dictionary + { + { "a", "1" }, + { "b", "2" }, + }, + }; + + await this.VerifyAsyncFunction(functionExamples.DictionaryToStringAsyncWrapper, args, JsonSerializer.Serialize(args.xargs, jsonSerializerOptions)); + this.VerifyFunctionDefinition(functionExamples.DictionaryToStringAsyncFunction); + } + + [Fact] + public async Task TopLevelFunctionExampleAddTestAsync() + { + var example = new TopLevelStatementFunctionExample(); + var args = new + { + a = 1, + b = 2, + }; + + await this.VerifyAsyncFunction(example.AddWrapper, args, "3"); + } + + [Fact] + public async Task FilescopeFunctionExampleAddTestAsync() + { + var example = new FilescopeNamespaceFunctionExample(); + var args = new + { + a = 1, + b = 2, + }; + + await this.VerifyAsyncFunction(example.AddWrapper, args, "3"); + } + + [Fact] + public void Query_Test() + { + var args = new + { + query = "hello", + k = 3, + }; + + this.VerifyFunction(functionExamples.QueryWrapper, args, new[] { "hello", "hello", "hello" }); + this.VerifyFunctionDefinition(functionExamples.QueryFunction); + } + + [UseReporter(typeof(DiffReporter))] + [UseApprovalSubdirectory("ApprovalTests")] + private void VerifyFunctionDefinition(FunctionDefinition function) + { + var func = new + { + name = function.Name, + description = function.Description.Replace(Environment.NewLine, ","), + parameters = function.Parameters.ToObjectFromJson(options: jsonSerializerOptions), + }; + + Approvals.Verify(JsonSerializer.Serialize(func, jsonSerializerOptions)); + } + + private void VerifyFunction(Func func, U args, T expected) + { + var str = JsonSerializer.Serialize(args, jsonSerializerOptions); + var res = func(str); + res.Should().BeEquivalentTo(expected); + } + + private async Task VerifyAsyncFunction(Func> func, U args, T expected) + { + var str = JsonSerializer.Serialize(args, jsonSerializerOptions); + var res = await func(str); + res.Should().BeEquivalentTo(expected); + } + } +} diff --git a/dotnet/test/AutoGen.SourceGenerator.Tests/FunctionExamples.cs b/dotnet/test/AutoGen.SourceGenerator.Tests/FunctionExamples.cs new file mode 100644 index 00000000000..d48906d2cd5 --- /dev/null +++ b/dotnet/test/AutoGen.SourceGenerator.Tests/FunctionExamples.cs @@ -0,0 +1,70 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// FunctionExamples.cs + +using System.Text.Json; +using AutoGen.Core; + +namespace AutoGen.SourceGenerator.Tests +{ + public partial class FunctionExamples + { + /// + /// Add function + /// + /// a + /// b + [FunctionAttribute] + public int Add(int a, int b) + { + return a + b; + } + + /// + /// Add two numbers. + /// + /// The first number. + /// The second number. + [Function] + public Task AddAsync(int a, int b) + { + return Task.FromResult($"{a} + {b} = {a + b}"); + } + + /// + /// Sum function + /// + /// an array of double values + [FunctionAttribute] + public double Sum(double[] args) + { + return args.Sum(); + } + + /// + /// DictionaryToString function + /// + /// an object of key-value pairs. key is string, value is string + [FunctionAttribute] + public Task DictionaryToStringAsync(Dictionary xargs) + { + var res = JsonSerializer.Serialize(xargs, new JsonSerializerOptions + { + WriteIndented = true, + }); + + return Task.FromResult(res); + } + + /// + /// query function + /// + /// query, required + /// top k, optional, default value is 3 + /// thresold, optional, default value is 0.5 + [FunctionAttribute] + public string[] Query(string query, int k = 3, float thresold = 0.5f) + { + return Enumerable.Repeat(query, k).ToArray(); + } + } +} diff --git a/dotnet/test/AutoGen.SourceGenerator.Tests/TopLevelStatementFunctionExample.cs b/dotnet/test/AutoGen.SourceGenerator.Tests/TopLevelStatementFunctionExample.cs new file mode 100644 index 00000000000..0acaa46a3fa --- /dev/null +++ b/dotnet/test/AutoGen.SourceGenerator.Tests/TopLevelStatementFunctionExample.cs @@ -0,0 +1,13 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// TopLevelStatementFunctionExample.cs + +using AutoGen.Core; + +public partial class TopLevelStatementFunctionExample +{ + [Function] + public Task Add(int a, int b) + { + return Task.FromResult($"{a + b}"); + } +} diff --git a/dotnet/test/AutoGen.Tests/ApprovalTests/square.png b/dotnet/test/AutoGen.Tests/ApprovalTests/square.png new file mode 100644 index 00000000000..afb4f4cd4df --- /dev/null +++ b/dotnet/test/AutoGen.Tests/ApprovalTests/square.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8323d0b8eceb752e14c29543b2e28bb2fc648ed9719095c31b7708867a4dc918 +size 491 diff --git a/dotnet/test/AutoGen.Tests/Attribute/EnvironmentSpecificFactAttribute.cs b/dotnet/test/AutoGen.Tests/Attribute/EnvironmentSpecificFactAttribute.cs new file mode 100644 index 00000000000..1042dec6f27 --- /dev/null +++ b/dotnet/test/AutoGen.Tests/Attribute/EnvironmentSpecificFactAttribute.cs @@ -0,0 +1,33 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// EnvironmentSpecificFactAttribute.cs + +using System; +using Xunit; + +namespace AutoGen.Tests +{ + /// + /// A base class for environment-specific fact attributes. + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + public abstract class EnvironmentSpecificFactAttribute : FactAttribute + { + private readonly string _skipMessage; + + /// + /// Creates a new instance of the class. + /// + /// The message to be used when skipping the test marked with this attribute. + protected EnvironmentSpecificFactAttribute(string skipMessage) + { + _skipMessage = skipMessage ?? throw new ArgumentNullException(nameof(skipMessage)); + } + + public sealed override string Skip => IsEnvironmentSupported() ? string.Empty : _skipMessage; + + /// + /// A method used to evaluate whether to skip a test marked with this attribute. Skips iff this method evaluates to false. + /// + protected abstract bool IsEnvironmentSupported(); + } +} diff --git a/dotnet/test/AutoGen.Tests/Attribute/OpenAIFact.cs b/dotnet/test/AutoGen.Tests/Attribute/OpenAIFact.cs new file mode 100644 index 00000000000..44457d8f571 --- /dev/null +++ b/dotnet/test/AutoGen.Tests/Attribute/OpenAIFact.cs @@ -0,0 +1,26 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// OpenAIFact.cs + +using System; +using System.Linq; + +namespace AutoGen.Tests +{ + /// + /// A fact for tests requiring OPENAI_API_KEY env. + /// + public sealed class ApiKeyFactAttribute : EnvironmentSpecificFactAttribute + { + private readonly string[] _envVariableNames; + public ApiKeyFactAttribute(params string[] envVariableNames) : base($"{envVariableNames} is not found in env") + { + _envVariableNames = envVariableNames; + } + + /// + protected override bool IsEnvironmentSupported() + { + return _envVariableNames.All(Environment.GetEnvironmentVariables().Contains); + } + } +} diff --git a/dotnet/test/AutoGen.Tests/AutoGen.Tests.csproj b/dotnet/test/AutoGen.Tests/AutoGen.Tests.csproj new file mode 100644 index 00000000000..740772c0407 --- /dev/null +++ b/dotnet/test/AutoGen.Tests/AutoGen.Tests.csproj @@ -0,0 +1,30 @@ +ο»Ώ + + + $(TestTargetFramework) + True + $(NoWarn);xUnit1013;SKEXP0110 + + + + + + + + + + + + + + + + + + + + PreserveNewest + + + + diff --git a/dotnet/test/AutoGen.Tests/BasicSampleTest.cs b/dotnet/test/AutoGen.Tests/BasicSampleTest.cs new file mode 100644 index 00000000000..b9eea67397c --- /dev/null +++ b/dotnet/test/AutoGen.Tests/BasicSampleTest.cs @@ -0,0 +1,91 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// BasicSampleTest.cs + +using System; +using System.IO; +using System.Threading.Tasks; +using AutoGen.BasicSample; +using Xunit.Abstractions; + +namespace AutoGen.Tests +{ + public class BasicSampleTest + { + private readonly ITestOutputHelper _output; + + public BasicSampleTest(ITestOutputHelper output) + { + _output = output; + Console.SetOut(new ConsoleWriter(_output)); + } + + [ApiKeyFact("AZURE_OPENAI_API_KEY", "AZURE_OPENAI_ENDPOINT")] + public async Task AssistantAgentTestAsync() + { + await Example01_AssistantAgent.RunAsync(); + } + + [ApiKeyFact("AZURE_OPENAI_API_KEY", "AZURE_OPENAI_ENDPOINT")] + public async Task TwoAgentMathClassTestAsync() + { + await Example02_TwoAgent_MathChat.RunAsync(); + } + + [ApiKeyFact("AZURE_OPENAI_API_KEY", "AZURE_OPENAI_ENDPOINT")] + public async Task AgentFunctionCallTestAsync() + { + await Example03_Agent_FunctionCall.RunAsync(); + } + + [ApiKeyFact("AZURE_OPENAI_API_KEY", "AZURE_OPENAI_ENDPOINT")] + public async Task OpenAIAgent_JsonMode() + { + await Example13_OpenAIAgent_JsonMode.RunAsync(); + } + + [ApiKeyFact("MISTRAL_API_KEY")] + public async Task MistralClientAgent_TokenCount() + { + await Example14_MistralClientAgent_TokenCount.RunAsync(); + } + + [ApiKeyFact("OPENAI_API_KEY")] + public async Task DynamicGroupChatGetMLNetPRTestAsync() + { + await Example04_Dynamic_GroupChat_Coding_Task.RunAsync(); + } + + [ApiKeyFact("AZURE_OPENAI_API_KEY", "AZURE_OPENAI_ENDPOINT")] + public async Task DynamicGroupChatCalculateFibonacciAsync() + { + await Example07_Dynamic_GroupChat_Calculate_Fibonacci.RunAsync(); + await Example07_Dynamic_GroupChat_Calculate_Fibonacci.RunWorkflowAsync(); + } + + [ApiKeyFact("OPENAI_API_KEY")] + public async Task DalleAndGPT4VTestAsync() + { + await Example05_Dalle_And_GPT4V.RunAsync(); + } + + [ApiKeyFact("OPENAI_API_KEY")] + public async Task GPT4ImageMessage() + { + await Example15_GPT4V_BinaryDataImageMessage.RunAsync(); + } + + public class ConsoleWriter : StringWriter + { + private ITestOutputHelper output; + public ConsoleWriter(ITestOutputHelper output) + { + this.output = output; + } + + public override void WriteLine(string? m) + { + output.WriteLine(m); + } + } + } +} diff --git a/dotnet/test/AutoGen.Tests/EchoAgent.cs b/dotnet/test/AutoGen.Tests/EchoAgent.cs new file mode 100644 index 00000000000..9cead5ad251 --- /dev/null +++ b/dotnet/test/AutoGen.Tests/EchoAgent.cs @@ -0,0 +1,41 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// EchoAgent.cs + +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +namespace AutoGen.Tests +{ + public class EchoAgent : IStreamingAgent + { + public EchoAgent(string name) + { + Name = name; + } + public string Name { get; } + + public Task GenerateReplyAsync( + IEnumerable conversation, + GenerateReplyOptions? options = null, + CancellationToken ct = default) + { + // return the most recent message + var lastMessage = conversation.Last(); + lastMessage.From = this.Name; + + return Task.FromResult(lastMessage); + } + + public async IAsyncEnumerable GenerateStreamingReplyAsync(IEnumerable messages, GenerateReplyOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + foreach (var message in messages) + { + message.From = this.Name; + yield return message; + } + } + } +} diff --git a/dotnet/test/AutoGen.Tests/GlobalUsing.cs b/dotnet/test/AutoGen.Tests/GlobalUsing.cs new file mode 100644 index 00000000000..d66bf001ed5 --- /dev/null +++ b/dotnet/test/AutoGen.Tests/GlobalUsing.cs @@ -0,0 +1,4 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// GlobalUsing.cs + +global using AutoGen.Core; diff --git a/dotnet/test/AutoGen.Tests/MathClassTest.cs b/dotnet/test/AutoGen.Tests/MathClassTest.cs new file mode 100644 index 00000000000..3f1eac76246 --- /dev/null +++ b/dotnet/test/AutoGen.Tests/MathClassTest.cs @@ -0,0 +1,242 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// MathClassTest.cs + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using AutoGen.OpenAI; +using FluentAssertions; +using Xunit.Abstractions; + +namespace AutoGen.Tests +{ + public partial class MathClassTest + { + private readonly ITestOutputHelper _output; + public MathClassTest(ITestOutputHelper output) + { + _output = output; + } + + [FunctionAttribute] + public async Task CreateMathQuestion(string question, int question_index) + { + return $@"// ignore this line [MATH_QUESTION] +Question #{question_index}: +{question}"; + } + + [FunctionAttribute] + public async Task AnswerQuestion(string answer) + { + return $@"// ignore this line [MATH_ANSWER] +The answer is {answer}, teacher please check answer"; + } + + [FunctionAttribute] + public async Task AnswerIsCorrect(string message) + { + return $@"// ignore this line [ANSWER_IS_CORRECT] +{message}"; + } + + [FunctionAttribute] + public async Task UpdateProgress(int correctAnswerCount) + { + if (correctAnswerCount >= 5) + { + return $@"// ignore this line [UPDATE_PROGRESS] +{GroupChatExtension.TERMINATE}"; + } + else + { + return $@"// ignore this line [UPDATE_PROGRESS] +the number of resolved question is {correctAnswerCount} +teacher, please create the next math question"; + } + } + + + [ApiKeyFact("AZURE_OPENAI_API_KEY", "AZURE_OPENAI_ENDPOINT")] + public async Task AssistantAgentMathChatTestAsync() + { + var teacher = await CreateTeacherAssistantAgentAsync(); + var student = await CreateStudentAssistantAgentAsync(); + var key = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY") ?? throw new ArgumentException("AZURE_OPENAI_API_KEY is not set"); + var endPoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new ArgumentException("AZURE_OPENAI_ENDPOINT is not set"); + var model = "gpt-35-turbo-16k"; + var admin = new GPTAgent( + name: "Admin", + systemMessage: $@"You are admin. You ask teacher to create 5 math questions. You update progress after each question is answered.", + config: new AzureOpenAIConfig(endPoint, model, key), + functions: new[] + { + this.UpdateProgressFunction, + }, + functionMap: new Dictionary>> + { + { this.UpdateProgressFunction.Name, this.UpdateProgressWrapper }, + }) + .RegisterMiddleware(async (messages, options, agent, ct) => + { + // check admin reply to make sure it calls UpdateProgress function + var maxAttempt = 5; + var reply = await agent.GenerateReplyAsync(messages, options, ct); + while (maxAttempt-- > 0) + { + if (options?.Functions is { Length: 0 }) + { + return reply; + } + + var formattedMessage = reply.FormatMessage(); + this._output.WriteLine(formattedMessage); + if (reply.GetContent()?.Contains("[UPDATE_PROGRESS]") is true) + { + return reply; + } + else + { + await Task.Delay(1000); + var review = "Admin, please update progress based on conversation"; + reply = await agent.SendAsync(review, messages, ct); + } + } + + throw new Exception("Admin does not call UpdateProgress function"); + }); + + await RunMathChatAsync(teacher, student, admin); + } + + private async Task CreateTeacherAssistantAgentAsync() + { + var key = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY") ?? throw new ArgumentException("AZURE_OPENAI_API_KEY is not set"); + var endPoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new ArgumentException("AZURE_OPENAI_ENDPOINT is not set"); + var model = "gpt-35-turbo-16k"; + var config = new AzureOpenAIConfig(endPoint, model, key); + var llmConfig = new ConversableAgentConfig + { + ConfigList = new[] + { + config, + }, + FunctionContracts = new[] + { + this.CreateMathQuestionFunctionContract, + this.AnswerIsCorrectFunctionContract, + }, + }; + + var teacher = new AssistantAgent( + name: "Teacher", + systemMessage: $@"You are a preschool math teacher. +You create math question and ask student to answer it. +Then you check if the answer is correct. +If the answer is wrong, you ask student to fix it. +If the answer is correct, you create another math question. +", + llmConfig: llmConfig, + functionMap: new Dictionary>> + { + { this.CreateMathQuestionFunction.Name, this.CreateMathQuestionWrapper }, + { this.AnswerIsCorrectFunction.Name, this.AnswerIsCorrectWrapper }, + }); + + return teacher; + } + + private async Task CreateStudentAssistantAgentAsync() + { + var key = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY") ?? throw new ArgumentException("AZURE_OPENAI_API_KEY is not set"); + var endPoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new ArgumentException("AZURE_OPENAI_ENDPOINT is not set"); + var model = "gpt-35-turbo-16k"; + var config = new AzureOpenAIConfig(endPoint, model, key); + var llmConfig = new ConversableAgentConfig + { + FunctionContracts = new[] + { + this.AnswerQuestionFunctionContract, + }, + ConfigList = new[] + { + config, + }, + }; + var student = new AssistantAgent( + name: "Student", + systemMessage: $@"You are a student. Here's your workflow in pseudo code: +-workflow- +answer_question +if answer is wrong + fix_answer +-end- + +Here are a few examples of answer_question: +-example 1- +2 + +Here are a few examples of fix_answer: +-example 1- +sorry, the answer should be 2, not 3 +", + llmConfig: llmConfig, + functionMap: new Dictionary>> + { + { this.AnswerQuestionFunction.Name, this.AnswerQuestionWrapper } + }); + + return student; + } + + private async Task RunMathChatAsync(IAgent teacher, IAgent student, IAgent admin) + { + var group = new GroupChat( + [ + admin, + teacher, + student, + ], + admin); + + admin.SendIntroduction($@"Welcome to the group chat! I'm admin", group); + teacher.SendIntroduction($@"Hey I'm Teacher", group); + student.SendIntroduction($@"Hey I'm Student", group); + admin.SendIntroduction(@$"Teacher, please create pre-school math question for student and check answer. +Student, for each question, please answer it and ask teacher to check if the answer is correct. +I'll update the progress after each question is answered. +The conversation will end after 5 correct answers. +", group); + + var groupChatManager = new GroupChatManager(group); + var chatHistory = await admin.InitiateChatAsync(groupChatManager, maxRound: 50); + + // print chat history + foreach (var message in chatHistory) + { + _output.WriteLine(message.FormatMessage()); + } + + // check if there's five questions from teacher + chatHistory.Where(msg => msg.From == teacher.Name && msg.GetContent()?.Contains("[MATH_QUESTION]") is true) + .Count() + .Should().BeGreaterThanOrEqualTo(5); + + // check if there's more than five answers from student (answer might be wrong) + chatHistory.Where(msg => msg.From == student.Name && msg.GetContent()?.Contains("[MATH_ANSWER]") is true) + .Count() + .Should().BeGreaterThanOrEqualTo(5); + + // check if there's five answer_is_correct from teacher + chatHistory.Where(msg => msg.From == teacher.Name && msg.GetContent()?.Contains("[ANSWER_IS_CORRECT]") is true) + .Count() + .Should().BeGreaterThanOrEqualTo(5); + + // check if there's terminate chat message from admin + chatHistory.Where(msg => msg.From == admin.Name && msg.IsGroupChatTerminateMessage()) + .Count() + .Should().Be(1); + } + } +} diff --git a/dotnet/test/AutoGen.Tests/MiddlewareAgentTest.cs b/dotnet/test/AutoGen.Tests/MiddlewareAgentTest.cs new file mode 100644 index 00000000000..9241c9e94f9 --- /dev/null +++ b/dotnet/test/AutoGen.Tests/MiddlewareAgentTest.cs @@ -0,0 +1,105 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// MiddlewareAgentTest.cs + +using System.Linq; +using System.Threading.Tasks; +using FluentAssertions; +using Xunit; + +namespace AutoGen.Tests; + +public class MiddlewareAgentTest +{ + [Fact] + public async Task MiddlewareAgentUseTestAsync() + { + IAgent echoAgent = new EchoAgent("echo"); + + var middlewareAgent = new MiddlewareAgent(echoAgent); + + // no middleware added + // the reply should be the same as the original agent + middlewareAgent.Name.Should().Be("echo"); + var reply = await middlewareAgent.SendAsync("hello"); + reply.GetContent().Should().Be("hello"); + + middlewareAgent.Use(async (messages, options, agent, ct) => + { + var lastMessage = messages.Last() as TextMessage; + lastMessage!.Content = $"[middleware 0] {lastMessage.Content}"; + return await agent.GenerateReplyAsync(messages, options, ct); + }); + + reply = await middlewareAgent.SendAsync("hello"); + reply.GetContent().Should().Be("[middleware 0] hello"); + + middlewareAgent.Use(async (messages, options, agent, ct) => + { + var lastMessage = messages.Last() as TextMessage; + lastMessage!.Content = $"[middleware 1] {lastMessage.Content}"; + return await agent.GenerateReplyAsync(messages, options, ct); + }); + + // when multiple middleware are added, they will be executed in LIFO order + reply = await middlewareAgent.SendAsync("hello"); + reply.GetContent().Should().Be("[middleware 0] [middleware 1] hello"); + + // test short cut + // short cut middleware will not call next middleware + middlewareAgent.Use(async (messages, options, next, ct) => + { + var lastMessage = messages.Last() as TextMessage; + lastMessage!.Content = $"[middleware shortcut] {lastMessage.Content}"; + return lastMessage; + }); + reply = await middlewareAgent.SendAsync("hello"); + reply.GetContent().Should().Be("[middleware shortcut] hello"); + } + + [Fact] + public async Task RegisterMiddlewareTestAsync() + { + var echoAgent = new EchoAgent("echo"); + + // RegisterMiddleware will return a new agent and keep the original agent unchanged + var middlewareAgent = echoAgent.RegisterMiddleware(async (messages, options, agent, ct) => + { + var lastMessage = messages.Last() as TextMessage; + lastMessage!.Content = $"[middleware 0] {lastMessage.Content}"; + return await agent.GenerateReplyAsync(messages, options, ct); + }); + + middlewareAgent.Should().BeOfType>(); + middlewareAgent.Middlewares.Count().Should().Be(1); + var reply = await middlewareAgent.SendAsync("hello"); + reply.GetContent().Should().Be("[middleware 0] hello"); + reply = await echoAgent.SendAsync("hello"); + reply.GetContent().Should().Be("hello"); + + // when multiple middleware are added, they will be executed in LIFO order + middlewareAgent = middlewareAgent.RegisterMiddleware(async (messages, options, agent, ct) => + { + var lastMessage = messages.Last() as TextMessage; + lastMessage!.Content = $"[middleware 1] {lastMessage.Content}"; + return await agent.GenerateReplyAsync(messages, options, ct); + }); + + middlewareAgent.Middlewares.Count().Should().Be(2); + reply = await middlewareAgent.SendAsync("hello"); + reply.GetContent().Should().Be("[middleware 0] [middleware 1] hello"); + + // test short cut + // short cut middleware will not call next middleware + middlewareAgent = middlewareAgent.RegisterMiddleware(async (messages, options, agent, ct) => + { + var lastMessage = messages.Last() as TextMessage; + lastMessage!.Content = $"[middleware shortcut] {lastMessage.Content}"; + return lastMessage; + }); + + reply = await middlewareAgent.SendAsync("hello"); + reply.GetContent().Should().Be("[middleware shortcut] hello"); + + middlewareAgent.Middlewares.Count().Should().Be(3); + } +} diff --git a/dotnet/test/AutoGen.Tests/MiddlewareTest.cs b/dotnet/test/AutoGen.Tests/MiddlewareTest.cs new file mode 100644 index 00000000000..6c1c89a33c1 --- /dev/null +++ b/dotnet/test/AutoGen.Tests/MiddlewareTest.cs @@ -0,0 +1,126 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// MiddlewareTest.cs + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using Azure.AI.OpenAI; +using FluentAssertions; +using Xunit; + +namespace AutoGen.Tests; + +public partial class MiddlewareTest +{ + [Function] + public async Task Echo(string message) + { + return $"[FUNC] {message}"; + } + + [Fact] + public async Task HumanInputMiddlewareTestAsync() + { + var agent = new EchoAgent("echo"); + var neverAskUserInputMW = new HumanInputMiddleware(mode: HumanInputMode.NEVER); + + var neverInputAgent = agent.RegisterMiddleware(neverAskUserInputMW); + var reply = await neverInputAgent.SendAsync("hello"); + reply.GetContent()!.Should().Be("hello"); + reply.From.Should().Be("echo"); + + var alwaysAskUserInputMW = new HumanInputMiddleware( + mode: HumanInputMode.ALWAYS, + getInput: () => "input"); + + var alwaysInputAgent = agent.RegisterMiddleware(alwaysAskUserInputMW); + reply = await alwaysInputAgent.SendAsync("hello"); + reply.GetContent()!.Should().Be("input"); + reply.From.Should().Be("echo"); + + // test auto mode + // if the reply from echo is not terminate message, return the original reply + var autoAskUserInputMW = new HumanInputMiddleware( + mode: HumanInputMode.AUTO, + isTermination: async (messages, ct) => messages.Last()?.GetContent() == "terminate", + getInput: () => "input", + exitKeyword: "exit"); + var autoInputAgent = agent.RegisterMiddleware(autoAskUserInputMW); + reply = await autoInputAgent.SendAsync("hello"); + reply.GetContent()!.Should().Be("hello"); + + // if the reply from echo is terminate message, asking user for input + reply = await autoInputAgent.SendAsync("terminate"); + reply.GetContent()!.Should().Be("input"); + + // if the reply from echo is terminate message, and user input is exit, return the TERMINATE message + autoAskUserInputMW = new HumanInputMiddleware( + mode: HumanInputMode.AUTO, + isTermination: async (messages, ct) => messages.Last().GetContent() == "terminate", + getInput: () => "exit", + exitKeyword: "exit"); + autoInputAgent = agent.RegisterMiddleware(autoAskUserInputMW); + + reply = await autoInputAgent.SendAsync("terminate"); + reply.IsGroupChatTerminateMessage().Should().BeTrue(); + } + + [Fact] + public async Task FunctionCallMiddlewareTestAsync() + { + var agent = new EchoAgent("echo"); + var args = new EchoSchema { message = "hello" }; + var argsJson = JsonSerializer.Serialize(args) ?? throw new InvalidOperationException("Failed to serialize args"); + var functionCall = new FunctionCall("echo", argsJson); + var functionCallAgent = agent.RegisterMiddleware(async (messages, options, agent, ct) => + { + if (options?.Functions is null) + { + return await agent.GenerateReplyAsync(messages, options, ct); + } + + return new ToolCallMessage(functionCall.Name, functionCall.Arguments, from: agent.Name); + }); + + // test 1 + // middleware should invoke function call if the message is a function call message + var mw = new FunctionCallMiddleware( + functionMap: new Dictionary>> { { "echo", EchoWrapper } }); + + var testAgent = agent.RegisterMiddleware(mw); + var functionCallMessage = new ToolCallMessage(functionCall.Name, functionCall.Arguments, from: "user"); + var reply = await testAgent.SendAsync(functionCallMessage); + reply.Should().BeOfType(); + reply.GetContent()!.Should().Be("[FUNC] hello"); + reply.From.Should().Be("echo"); + + // test 2 + // middleware should invoke function call if agent reply is a function call message + mw = new FunctionCallMiddleware( + functions: [this.EchoFunctionContract], + functionMap: new Dictionary>> { { "echo", EchoWrapper } }); + testAgent = functionCallAgent.RegisterMiddleware(mw); + reply = await testAgent.SendAsync("hello"); + reply.GetContent()!.Should().Be("[FUNC] hello"); + reply.From.Should().Be("echo"); + + // test 3 + // middleware should return original reply if the reply from agent is not a function call message + mw = new FunctionCallMiddleware( + functionMap: new Dictionary>> { { "echo", EchoWrapper } }); + testAgent = agent.RegisterMiddleware(mw); + reply = await testAgent.SendAsync("hello"); + reply.GetContent()!.Should().Be("hello"); + reply.From.Should().Be("echo"); + + // test 4 + // middleware should return an error message if the function name is not available when invoking the function from previous agent reply + mw = new FunctionCallMiddleware( + functionMap: new Dictionary>> { { "echo2", EchoWrapper } }); + testAgent = agent.RegisterMiddleware(mw); + reply = await testAgent.SendAsync(functionCallMessage); + reply.GetContent()!.Should().Be("Function echo is not available. Available functions are: echo2"); + } +} diff --git a/dotnet/test/AutoGen.Tests/SingleAgentTest.cs b/dotnet/test/AutoGen.Tests/SingleAgentTest.cs new file mode 100644 index 00000000000..79d2b9c2f3f --- /dev/null +++ b/dotnet/test/AutoGen.Tests/SingleAgentTest.cs @@ -0,0 +1,375 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// SingleAgentTest.cs + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using AutoGen.LMStudio; +using AutoGen.OpenAI; +using Azure.AI.OpenAI; +using FluentAssertions; +using Xunit; +using Xunit.Abstractions; + +namespace AutoGen.Tests +{ + public partial class SingleAgentTest + { + private ITestOutputHelper _output; + public SingleAgentTest(ITestOutputHelper output) + { + _output = output; + } + + private ILLMConfig CreateAzureOpenAIGPT35TurboConfig() + { + var key = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY") ?? throw new ArgumentException("AZURE_OPENAI_API_KEY is not set"); + var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new ArgumentException("AZURE_OPENAI_ENDPOINT is not set"); + return new AzureOpenAIConfig(endpoint, "gpt-35-turbo-16k", key); + } + + private ILLMConfig CreateOpenAIGPT4VisionConfig() + { + var key = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new ArgumentException("OPENAI_API_KEY is not set"); + return new OpenAIConfig(key, "gpt-4-vision-preview"); + } + + [ApiKeyFact("AZURE_OPENAI_API_KEY", "AZURE_OPENAI_ENDPOINT")] + public async Task GPTAgentTestAsync() + { + var config = this.CreateAzureOpenAIGPT35TurboConfig(); + + var agent = new GPTAgent("gpt", "You are a helpful AI assistant", config); + + await UpperCaseTestAsync(agent); + await UpperCaseStreamingTestAsync(agent); + } + + [ApiKeyFact("OPENAI_API_KEY", "AZURE_OPENAI_API_KEY", "AZURE_OPENAI_ENDPOINT")] + public async Task GPTAgentVisionTestAsync() + { + var visionConfig = this.CreateOpenAIGPT4VisionConfig(); + var visionAgent = new GPTAgent( + name: "gpt", + systemMessage: "You are a helpful AI assistant", + config: visionConfig, + temperature: 0); + + var gpt3Config = this.CreateAzureOpenAIGPT35TurboConfig(); + var gpt3Agent = new GPTAgent( + name: "gpt3", + systemMessage: "You are a helpful AI assistant, return highest label from conversation", + config: gpt3Config, + temperature: 0, + functions: new[] { this.GetHighestLabelFunction }, + functionMap: new Dictionary>> + { + { nameof(GetHighestLabel), this.GetHighestLabelWrapper }, + }); + + var imageUri = new Uri(@"https://microsoft.github.io/autogen/assets/images/level2algebra-659ba95286432d9945fc89e84d606797.png"); + var oaiMessage = new ChatRequestUserMessage( + new ChatMessageTextContentItem("which label has the highest inference cost"), + new ChatMessageImageContentItem(imageUri)); + var multiModalMessage = new MultiModalMessage(Role.User, + [ + new TextMessage(Role.User, "which label has the highest inference cost", from: "user"), + new ImageMessage(Role.User, imageUri, from: "user"), + ], + from: "user"); + + var imageMessage = new ImageMessage(Role.User, imageUri, from: "user"); + + string imagePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ApprovalTests", "square.png"); + ImageMessage imageMessageData; + using (var fs = new FileStream(imagePath, FileMode.Open, FileAccess.Read)) + { + var ms = new MemoryStream(); + await fs.CopyToAsync(ms); + ms.Seek(0, SeekOrigin.Begin); + var imageData = await BinaryData.FromStreamAsync(ms, "image/png"); + imageMessageData = new ImageMessage(Role.Assistant, imageData, from: "user"); + } + + IMessage[] messages = [ + MessageEnvelope.Create(oaiMessage), + multiModalMessage, + imageMessage, + imageMessageData + ]; + + foreach (var message in messages) + { + var response = await visionAgent.SendAsync(message); + response.From.Should().Be(visionAgent.Name); + + var labelResponse = await gpt3Agent.SendAsync(response); + labelResponse.From.Should().Be(gpt3Agent.Name); + labelResponse.GetToolCalls()!.First().FunctionName.Should().Be(nameof(GetHighestLabel)); + } + } + + [ApiKeyFact("AZURE_OPENAI_API_KEY", "AZURE_OPENAI_ENDPOINT")] + public async Task GPTFunctionCallAgentTestAsync() + { + var config = this.CreateAzureOpenAIGPT35TurboConfig(); + var agentWithFunction = new GPTAgent("gpt", "You are a helpful AI assistant", config, 0, functions: new[] { this.EchoAsyncFunction }); + + await EchoFunctionCallTestAsync(agentWithFunction); + await UpperCaseTestAsync(agentWithFunction); + } + + [ApiKeyFact("AZURE_OPENAI_API_KEY", "AZURE_OPENAI_ENDPOINT")] + public async Task AssistantAgentFunctionCallTestAsync() + { + var config = this.CreateAzureOpenAIGPT35TurboConfig(); + + var llmConfig = new ConversableAgentConfig + { + Temperature = 0, + FunctionContracts = new[] + { + this.EchoAsyncFunctionContract, + }, + ConfigList = new[] + { + config, + }, + }; + + var assistantAgent = new AssistantAgent( + name: "assistant", + llmConfig: llmConfig); + + await EchoFunctionCallTestAsync(assistantAgent); + await UpperCaseTestAsync(assistantAgent); + } + + [Fact] + public async Task ItCreateAssistantAgentFromLMStudioConfigAsync() + { + var host = "http://localhost"; + var port = 8080; + var lmStudioConfig = new LMStudioConfig(host, port); + + var assistantAgent = new AssistantAgent( + name: "assistant", + llmConfig: new ConversableAgentConfig() + { + ConfigList = [lmStudioConfig], + }); + + assistantAgent.Name.Should().Be("assistant"); + assistantAgent.InnerAgent.Should().BeOfType(); + } + + [ApiKeyFact("LMStudio_ENDPOINT")] + public async Task ItTestAssistantAgentFromLMStudioConfigAsync() + { + var Uri = Environment.GetEnvironmentVariable("LMStudio_ENDPOINT") ?? throw new ArgumentException("LMStudio_ENDPOINT is not set"); + var lmStudioConfig = new LMStudioConfig(new Uri(Uri)); + + var assistantAgent = new AssistantAgent( + name: "assistant", + llmConfig: new ConversableAgentConfig() + { + ConfigList = [lmStudioConfig], + }); + + assistantAgent.Name.Should().Be("assistant"); + assistantAgent.InnerAgent.Should().BeOfType(); + await this.UpperCaseTestAsync(assistantAgent); + } + + + [Fact] + public async Task AssistantAgentDefaultReplyTestAsync() + { + var assistantAgent = new AssistantAgent( + llmConfig: null, + name: "assistant", + defaultReply: "hello world"); + + var reply = await assistantAgent.SendAsync("hi"); + + reply.GetContent().Should().Be("hello world"); + reply.GetRole().Should().Be(Role.Assistant); + reply.From.Should().Be(assistantAgent.Name); + } + + [ApiKeyFact("AZURE_OPENAI_API_KEY", "AZURE_OPENAI_ENDPOINT")] + public async Task AssistantAgentFunctionCallSelfExecutionTestAsync() + { + var config = this.CreateAzureOpenAIGPT35TurboConfig(); + var llmConfig = new ConversableAgentConfig + { + FunctionContracts = new[] + { + this.EchoAsyncFunctionContract, + }, + ConfigList = new[] + { + config, + }, + }; + var assistantAgent = new AssistantAgent( + name: "assistant", + llmConfig: llmConfig, + functionMap: new Dictionary>> + { + { nameof(EchoAsync), this.EchoAsyncWrapper }, + }); + + await EchoFunctionCallExecutionTestAsync(assistantAgent); + } + + [ApiKeyFact("AZURE_OPENAI_API_KEY", "AZURE_OPENAI_ENDPOINT")] + public async Task GPTAgentFunctionCallSelfExecutionTestAsync() + { + var config = this.CreateAzureOpenAIGPT35TurboConfig(); + var agent = new GPTAgent( + name: "gpt", + systemMessage: "You are a helpful AI assistant", + config: config, + temperature: 0, + functions: new[] { this.EchoAsyncFunction }, + functionMap: new Dictionary>> + { + { nameof(EchoAsync), this.EchoAsyncWrapper }, + }); + + await EchoFunctionCallExecutionStreamingTestAsync(agent); + await EchoFunctionCallExecutionTestAsync(agent); + await UpperCaseTestAsync(agent); + } + + /// + /// echo when asked. + /// + /// message to echo + [FunctionAttribute] + public async Task EchoAsync(string message) + { + return $"[ECHO] {message}"; + } + + /// + /// return the label name with hightest inference cost + /// + /// + /// + [FunctionAttribute] + public async Task GetHighestLabel(string labelName, string color) + { + return $"[HIGHEST_LABEL] {labelName} {color}"; + } + + public async Task EchoFunctionCallTestAsync(IAgent agent) + { + var message = new TextMessage(Role.System, "You are a helpful AI assistant that call echo function"); + var helloWorld = new TextMessage(Role.User, "echo Hello world"); + + var reply = await agent.SendAsync(chatHistory: new[] { message, helloWorld }); + + reply.From.Should().Be(agent.Name); + reply.GetToolCalls()!.First().FunctionName.Should().Be(nameof(EchoAsync)); + } + + public async Task EchoFunctionCallExecutionTestAsync(IAgent agent) + { + var message = new TextMessage(Role.System, "You are a helpful AI assistant that echo whatever user says"); + var helloWorld = new TextMessage(Role.User, "echo Hello world"); + + var reply = await agent.SendAsync(chatHistory: new[] { message, helloWorld }); + + reply.GetContent().Should().Be("[ECHO] Hello world"); + reply.From.Should().Be(agent.Name); + reply.Should().BeOfType>(); + } + + public async Task EchoFunctionCallExecutionStreamingTestAsync(IStreamingAgent agent) + { + var message = new TextMessage(Role.System, "You are a helpful AI assistant that echo whatever user says"); + var helloWorld = new TextMessage(Role.User, "echo Hello world"); + var option = new GenerateReplyOptions + { + Temperature = 0, + }; + var replyStream = agent.GenerateStreamingReplyAsync(messages: new[] { message, helloWorld }, option); + var answer = "[ECHO] Hello world"; + IStreamingMessage? finalReply = default; + await foreach (var reply in replyStream) + { + reply.From.Should().Be(agent.Name); + finalReply = reply; + } + + if (finalReply is AggregateMessage aggregateMessage) + { + var toolCallResultMessage = aggregateMessage.Message2; + toolCallResultMessage.ToolCalls.First().Result.Should().Be(answer); + toolCallResultMessage.From.Should().Be(agent.Name); + toolCallResultMessage.ToolCalls.First().FunctionName.Should().Be(nameof(EchoAsync)); + } + else + { + throw new Exception("unexpected message type"); + } + } + + public async Task UpperCaseTestAsync(IAgent agent) + { + var message = new TextMessage(Role.System, "You are a helpful AI assistant that convert user message to upper case"); + var uppCaseMessage = new TextMessage(Role.User, "abcdefg"); + + var reply = await agent.SendAsync(chatHistory: new[] { message, uppCaseMessage }); + + reply.GetContent().Should().Contain("ABCDEFG"); + reply.From.Should().Be(agent.Name); + } + + public async Task UpperCaseStreamingTestAsync(IStreamingAgent agent) + { + var message = new TextMessage(Role.System, "You are a helpful AI assistant that convert user message to upper case"); + var helloWorld = new TextMessage(Role.User, "a b c d e f g h i j k l m n"); + var option = new GenerateReplyOptions + { + Temperature = 0, + }; + var replyStream = agent.GenerateStreamingReplyAsync(messages: new[] { message, helloWorld }, option); + var answer = "A B C D E F G H I J K L M N"; + TextMessage? finalReply = default; + await foreach (var reply in replyStream) + { + if (reply is TextMessageUpdate update) + { + update.From.Should().Be(agent.Name); + + if (finalReply is null) + { + finalReply = new TextMessage(update); + } + else + { + finalReply.Update(update); + } + + continue; + } + else if (reply is TextMessage textMessage) + { + finalReply = textMessage; + continue; + } + + throw new Exception("unexpected message type"); + } + + finalReply!.Content.Should().Contain(answer); + finalReply!.Role.Should().Be(Role.Assistant); + finalReply!.From.Should().Be(agent.Name); + } + } +} diff --git a/dotnet/test/AutoGen.Tests/TwoAgentTest.cs b/dotnet/test/AutoGen.Tests/TwoAgentTest.cs new file mode 100644 index 00000000000..91437eaa618 --- /dev/null +++ b/dotnet/test/AutoGen.Tests/TwoAgentTest.cs @@ -0,0 +1,106 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// TwoAgentTest.cs +#pragma warning disable xUnit1013 +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using AutoGen.OpenAI; +using FluentAssertions; +using Xunit.Abstractions; + +namespace AutoGen.Tests; + +public partial class TwoAgentTest +{ + private ITestOutputHelper _output; + public TwoAgentTest(ITestOutputHelper output) + { + _output = output; + } + + [Function] + public async Task GetWeather(string city) + { + return $"[GetWeatherFunction] The weather in {city} is sunny"; + } + + [ApiKeyFact("AZURE_OPENAI_API_KEY", "AZURE_OPENAI_ENDPOINT")] + public async Task TwoAgentWeatherChatTestAsync() + { + var key = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY") ?? throw new ArgumentException("AZURE_OPENAI_API_KEY is not set"); + var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new ArgumentException("AZURE_OPENAI_ENDPOINT is not set"); + var deploymentName = "gpt-35-turbo-16k"; + var config = new AzureOpenAIConfig(endpoint, deploymentName, key); + + var assistant = new AssistantAgent( + "assistant", + llmConfig: new ConversableAgentConfig + { + ConfigList = new[] { config }, + FunctionContracts = new[] + { + this.GetWeatherFunctionContract, + }, + }) + .RegisterMiddleware(async (msgs, option, agent, ct) => + { + var reply = await agent.GenerateReplyAsync(msgs, option, ct); + var format = reply.FormatMessage(); + _output.WriteLine(format); + + return reply; + }); + + var user = new UserProxyAgent( + name: "user", + functionMap: new Dictionary>> + { + { this.GetWeatherFunction.Name, this.GetWeatherWrapper }, + }) + .RegisterMiddleware(async (msgs, option, agent, ct) => + { + var lastMessage = msgs.Last(); + if (lastMessage.GetToolCalls()?.FirstOrDefault()?.FunctionName != null) + { + return await agent.GenerateReplyAsync(msgs, option, ct); + } + else + { + // terminate message + return new Message(Role.Assistant, GroupChatExtension.TERMINATE); + } + }) + .RegisterMiddleware(async (msgs, option, agent, ct) => + { + var reply = await agent.GenerateReplyAsync(msgs, option, ct); + var format = reply.FormatMessage(); + _output.WriteLine(format); + + return reply; + }); + + var chatHistory = (await user.InitiateChatAsync(assistant, "what's weather in New York", 10)).ToArray(); + + // the last message should be terminated message + chatHistory.Last().IsGroupChatTerminateMessage().Should().BeTrue(); + + // the third last message should be the weather message from function + chatHistory[^3].GetContent().Should().Be("[GetWeatherFunction] The weather in New York is sunny"); + + // the # of messages should be 5 + chatHistory.Length.Should().Be(5); + } + + public async Task TwoAgentGetWeatherFunctionCallTestAsync(IAgent user, IAgent assistant) + { + var question = new TextMessage(Role.Assistant, "what's the weather in Seattle", from: user.Name); + var assistantReply = await assistant.SendAsync(question); + assistantReply.Should().BeOfType(); + var toolCallResult = await user.SendAsync(chatHistory: [question, assistantReply]); + toolCallResult.Should().BeOfType(); + var finalReply = await assistant.SendAsync(chatHistory: [question, assistantReply, toolCallResult]); + finalReply.Should().BeOfType(); + finalReply.GetContent()!.ToLower().Should().Contain("sunny"); + } +} diff --git a/dotnet/test/AutoGen.Tests/WorkflowTest.cs b/dotnet/test/AutoGen.Tests/WorkflowTest.cs new file mode 100644 index 00000000000..d57cf2126c4 --- /dev/null +++ b/dotnet/test/AutoGen.Tests/WorkflowTest.cs @@ -0,0 +1,70 @@ +ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. +// WorkflowTest.cs + +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using FluentAssertions; +using Xunit; + +namespace AutoGen.Tests; + +public class WorkflowTest +{ + [Fact] + public async Task TransitionTestAsync() + { + var alice = new EchoAgent("alice"); + var bob = new EchoAgent("bob"); + + var aliceToBob = Transition.Create(alice, bob, async (from, to, messages) => + { + if (messages.Any(m => m.GetContent() == "Hello")) + { + return true; + } + + return false; + }); + + var canTransit = await aliceToBob.CanTransitionAsync([]); + canTransit.Should().BeFalse(); + + canTransit = await aliceToBob.CanTransitionAsync(new[] { new Message(Role.Assistant, "Hello") }); + canTransit.Should().BeTrue(); + + // if no function is provided, it should always return true + var aliceToBobNoFunction = Transition.Create(alice, bob); + canTransit = await aliceToBobNoFunction.CanTransitionAsync(new[] { new Message(Role.Assistant, "Hello") }); + canTransit.Should().BeTrue(); + } + + [Fact] + public async Task WorkflowBasicTestAsync() + { + var alice = new EchoAgent("alice"); + var bob = new EchoAgent("bob"); + var charlie = new EchoAgent("charlie"); + + // alice can speak to bob + // bob can speak to charlie + // charlie can speak to alice + + var aliceToBob = Transition.Create(alice, bob); + var bobToCharlie = Transition.Create(bob, charlie); + var charlieToAlice = Transition.Create(charlie, alice); + var workflow = new Graph([aliceToBob, bobToCharlie, charlieToAlice]); + IAgent currentAgent = alice; + var agentNames = new List(); + do + { + agentNames.Add(currentAgent.Name!); + var nextAgents = await workflow.TransitToNextAvailableAgentsAsync(currentAgent, []); + nextAgents.Count().Should().Be(1); + currentAgent = nextAgents.First(); + } + while (currentAgent != alice); + + agentNames.Should().BeEquivalentTo(["alice", "bob", "charlie"]); + } +} diff --git a/dotnet/test/Autogen.Ollama.Tests/Autogen.Ollama.Tests.csproj b/dotnet/test/Autogen.Ollama.Tests/Autogen.Ollama.Tests.csproj new file mode 100644 index 00000000000..a10ce496ae7 --- /dev/null +++ b/dotnet/test/Autogen.Ollama.Tests/Autogen.Ollama.Tests.csproj @@ -0,0 +1,33 @@ + + + + net8.0 + enable + enable + + false + true + True + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + diff --git a/dotnet/test/Autogen.Ollama.Tests/OllamaAgentTests.cs b/dotnet/test/Autogen.Ollama.Tests/OllamaAgentTests.cs new file mode 100644 index 00000000000..b22432fdad6 --- /dev/null +++ b/dotnet/test/Autogen.Ollama.Tests/OllamaAgentTests.cs @@ -0,0 +1,102 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// OllamaAgentTests.cs + +using System.Text.Json; +using AutoGen.Core; +using AutoGen.Tests; +using FluentAssertions; + +namespace Autogen.Ollama.Tests; + +public class OllamaAgentTests +{ + + [ApiKeyFact("OLLAMA_HOST", "OLLAMA_MODEL_NAME")] + public async Task GenerateReplyAsync_ReturnsValidMessage_WhenCalled() + { + string host = Environment.GetEnvironmentVariable("OLLAMA_HOST") + ?? throw new InvalidOperationException("OLLAMA_HOST is not set."); + string modelName = Environment.GetEnvironmentVariable("OLLAMA_MODEL_NAME") + ?? throw new InvalidOperationException("OLLAMA_MODEL_NAME is not set."); + OllamaAgent ollamaAgent = BuildOllamaAgent(host, modelName); + + var messages = new IMessage[] { new TextMessage(Role.User, "Hello, how are you") }; + IMessage result = await ollamaAgent.GenerateReplyAsync(messages); + + result.Should().NotBeNull(); + result.Should().BeOfType>(); + result.From.Should().Be(ollamaAgent.Name); + } + + [ApiKeyFact("OLLAMA_HOST", "OLLAMA_MODEL_NAME")] + public async Task GenerateReplyAsync_ReturnsValidJsonMessageContent_WhenCalled() + { + string host = Environment.GetEnvironmentVariable("OLLAMA_HOST") + ?? throw new InvalidOperationException("OLLAMA_HOST is not set."); + string modelName = Environment.GetEnvironmentVariable("OLLAMA_MODEL_NAME") + ?? throw new InvalidOperationException("OLLAMA_MODEL_NAME is not set."); + OllamaAgent ollamaAgent = BuildOllamaAgent(host, modelName); + + var messages = new IMessage[] { new TextMessage(Role.User, "Hello, how are you") }; + IMessage result = await ollamaAgent.GenerateReplyAsync(messages, new OllamaReplyOptions + { + Format = FormatType.Json + }); + + result.Should().NotBeNull(); + result.Should().BeOfType>(); + result.From.Should().Be(ollamaAgent.Name); + + string jsonContent = ((MessageEnvelope)result).Content.Message!.Value; + bool isValidJson = IsValidJsonMessage(jsonContent); + isValidJson.Should().BeTrue(); + } + + [ApiKeyFact("OLLAMA_HOST", "OLLAMA_MODEL_NAME")] + public async Task GenerateStreamingReplyAsync_ReturnsValidMessages_WhenCalled() + { + string host = Environment.GetEnvironmentVariable("OLLAMA_HOST") + ?? throw new InvalidOperationException("OLLAMA_HOST is not set."); + string modelName = Environment.GetEnvironmentVariable("OLLAMA_MODEL_NAME") + ?? throw new InvalidOperationException("OLLAMA_MODEL_NAME is not set."); + OllamaAgent ollamaAgent = BuildOllamaAgent(host, modelName); + + var messages = new IMessage[] { new TextMessage(Role.User, "Hello how are you") }; + IStreamingMessage? finalReply = default; + await foreach (IStreamingMessage message in ollamaAgent.GenerateStreamingReplyAsync(messages)) + { + message.Should().NotBeNull(); + message.From.Should().Be(ollamaAgent.Name); + finalReply = message; + } + + finalReply.Should().BeOfType>(); + } + + private static bool IsValidJsonMessage(string input) + { + try + { + JsonDocument.Parse(input); + return true; + } + catch (JsonException) + { + return false; + } + catch (Exception ex) + { + Console.WriteLine("An unexpected exception occurred: " + ex.Message); + return false; + } + } + + private static OllamaAgent BuildOllamaAgent(string host, string modelName) + { + var httpClient = new HttpClient + { + BaseAddress = new Uri(host) + }; + return new OllamaAgent(httpClient, "TestAgent", modelName); + } +} diff --git a/dotnet/test/Autogen.Ollama.Tests/OllamaTextEmbeddingServiceTests.cs b/dotnet/test/Autogen.Ollama.Tests/OllamaTextEmbeddingServiceTests.cs new file mode 100644 index 00000000000..7f2d94e147d --- /dev/null +++ b/dotnet/test/Autogen.Ollama.Tests/OllamaTextEmbeddingServiceTests.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// OllamaTextEmbeddingServiceTests.cs + +using AutoGen.Tests; +using FluentAssertions; + +namespace Autogen.Ollama.Tests; + +public class OllamaTextEmbeddingServiceTests +{ + [ApiKeyFact("OLLAMA_HOST", "OLLAMA_EMBEDDING_MODEL_NAME")] + public async Task GenerateAsync_ReturnsEmbeddings_WhenApiResponseIsSuccessful() + { + string host = Environment.GetEnvironmentVariable("OLLAMA_HOST") + ?? throw new InvalidOperationException("OLLAMA_HOST is not set."); + string embeddingModelName = Environment.GetEnvironmentVariable("OLLAMA_EMBEDDING_MODEL_NAME") + ?? throw new InvalidOperationException("OLLAMA_EMBEDDING_MODEL_NAME is not set."); + var httpClient = new HttpClient + { + BaseAddress = new Uri(host) + }; + var request = new TextEmbeddingsRequest { Model = embeddingModelName, Prompt = "Llamas are members of the camelid family", }; + var service = new OllamaTextEmbeddingService(httpClient); + TextEmbeddingsResponse response = await service.GenerateAsync(request); + response.Should().NotBeNull(); + } +} diff --git a/dotnet/website/.gitignore b/dotnet/website/.gitignore new file mode 100644 index 00000000000..8d5bc9f4490 --- /dev/null +++ b/dotnet/website/.gitignore @@ -0,0 +1,12 @@ +############### +# folder # +############### +/**/DROP/ +/**/TEMP/ +/**/packages/ +/**/bin/ +/**/obj/ + +# build artifacts for web +_site/ +api/ diff --git a/dotnet/website/README.md b/dotnet/website/README.md new file mode 100644 index 00000000000..fd587ad2807 --- /dev/null +++ b/dotnet/website/README.md @@ -0,0 +1,13 @@ +## How to build and run the website + +### Prerequisites +- dotnet 7.0 or later + +### Build +Firstly, go to autogen/dotnet folder and run the following command to build the website: +```bash +dotnet tool restore +dotnet tool run docfx website/docfx.json --serve +``` + +After the command is executed, you can open your browser and navigate to `http://localhost:8080` to view the website. \ No newline at end of file diff --git a/dotnet/website/articles/Agent-overview.md b/dotnet/website/articles/Agent-overview.md new file mode 100644 index 00000000000..08aa7a93da1 --- /dev/null +++ b/dotnet/website/articles/Agent-overview.md @@ -0,0 +1,44 @@ +`Agent` is one of the most fundamental concepts in AutoGen.Net. In AutoGen.Net, you construct a single agent to process a specific task, and you extend an agent using [Middlewares](./Middleware-overview.md), and you construct a multi-agent workflow using [GroupChat](./Group-chat-overview.md). + +> [!NOTE] +> Every agent in AutoGen.Net implements @AutoGen.Core.IAgent, for agent that supports streaming reply, it also implements @AutoGen.Core.IStreamingAgent. + +## Create an agent +- Create an @AutoGen.AssistantAgent: [Create an assistant agent](./Create-an-agent.md) +- Create an @AutoGen.OpenAI.OpenAIChatAgent: [Create an OpenAI chat agent](./OpenAIChatAgent-simple-chat.md) +- Create a @AutoGen.SemanticKernel.SemanticKernelAgent: [Create a semantic kernel agent](./SemanticKernelAgent-simple-chat.md) +- Create a @AutoGen.LMStudio.LMStudioAgent: [Connect to LM Studio](./Consume-LLM-server-from-LM-Studio.md) +- Create your own agent: [Create your own agent](./Create-your-own-agent.md) + +## Chat with an agent +To chat with an agent, typically you can invoke @AutoGen.Core.IAgent.GenerateReplyAsync*. On top of that, you can also use one of the extension methods like @AutoGen.Core.AgentExtension.SendAsync* as shortcuts. + +> [!NOTE] +> AutoGen provides a list of built-in message types like @AutoGen.Core.TextMessage, @AutoGen.Core.ImageMessage, @AutoGen.Core.MultiModalMessage, @AutoGen.Core.ToolCallMessage, @AutoGen.Core.ToolCallResultMessage, etc. You can use these message types to chat with an agent. For further details, see [built-in messages](./Built-in-messages.md). + +- Send a @AutoGen.Core.TextMessage to an agent via @AutoGen.Core.IAgent.GenerateReplyAsync*: +[!code-csharp[](../../sample/AutoGen.BasicSamples/CodeSnippet/AgentCodeSnippet.cs?name=ChatWithAnAgent_GenerateReplyAsync)] + +- Send a message to an agent via @AutoGen.Core.AgentExtension.SendAsync*: +[!code-csharp[](../../sample/AutoGen.BasicSamples/CodeSnippet/AgentCodeSnippet.cs?name=ChatWithAnAgent_SendAsync)] + +## Streaming chat +If an agent implements @AutoGen.Core.IStreamingAgent, you can use @AutoGen.Core.IStreamingAgent.GenerateStreamingReplyAsync* to chat with the agent in a streaming way. You would need to process the streaming updates on your side though. + +- Send a @AutoGen.Core.TextMessage to an agent via @AutoGen.Core.IStreamingAgent.GenerateStreamingReplyAsync*, and print the streaming updates to console: +[!code-csharp[](../../sample/AutoGen.BasicSamples/CodeSnippet/AgentCodeSnippet.cs?name=ChatWithAnAgent_GenerateStreamingReplyAsync)] + +## Register middleware to an agent +@AutoGen.Core.IMiddleware and @AutoGen.Core.IStreamingMiddleware are used to extend the behavior of @AutoGen.Core.IAgent.GenerateReplyAsync* and @AutoGen.Core.IStreamingAgent.GenerateStreamingReplyAsync*. You can register middleware to an agent to customize the behavior of the agent on things like function call support, converting message of different types, print message, gather user input, etc. + +- Middleware overview: [Middleware overview](./Middleware-overview.md) +- Write message to console: [Print message middleware](./Print-message-middleware.md) +- Convert message type: [SemanticKernelChatMessageContentConnector](./SemanticKernelAgent-support-more-messages.md) and [OpenAIChatRequestMessageConnector](./OpenAIChatAgent-support-more-messages.md) +- Create your own middleware: [Create your own middleware](./Create-your-own-middleware.md) + +## Group chat +You can construct a multi-agent workflow using @AutoGen.Core.IGroupChat. In AutoGen.Net, there are two type of group chat: +@AutoGen.Core.SequentialGroupChat: Orchestrates the agents in the group chat in a fix, sequential order. +@AutoGen.Core.GroupChat: Provide more dynamic yet controllable way to orchestrate the agents in the group chat. + +For further details, see [Group chat overview](./Group-chat-overview.md). \ No newline at end of file diff --git a/dotnet/website/articles/AutoGen-Mistral-Overview.md b/dotnet/website/articles/AutoGen-Mistral-Overview.md new file mode 100644 index 00000000000..df5e154d05e --- /dev/null +++ b/dotnet/website/articles/AutoGen-Mistral-Overview.md @@ -0,0 +1,26 @@ +## AutoGen.Mistral overview + +AutoGen.Mistral provides the following agent(s) to connect to [Mistral.AI](https://mistral.ai/) platform. +- @AutoGen.Mistral.MistralClientAgent: A slim wrapper agent over @AutoGen.Mistral.MistralClient. + +### Get started with AutoGen.Mistral + +To get started with AutoGen.Mistral, follow the [installation guide](Installation.md) to make sure you add the AutoGen feed correctly. Then add the `AutoGen.Mistral` package to your project file. + +```bash +dotnet add package AutoGen.Mistral +``` + +>[!NOTE] +> You need to provide an api-key to use Mistral models which will bring additional cost while using. you can get the api key from [Mistral.AI](https://mistral.ai/). + +### Example + +Import the required namespace +[!code-csharp[](../../sample/AutoGen.BasicSamples/CodeSnippet/MistralAICodeSnippet.cs?name=using_statement)] + +Create a @AutoGen.Mistral.MistralClientAgent and start chatting! +[!code-csharp[](../../sample/AutoGen.BasicSamples/CodeSnippet/MistralAICodeSnippet.cs?name=create_mistral_agent)] + +Use @AutoGen.Core.IStreamingAgent.GenerateStreamingReplyAsync* to stream the chat completion. +[!code-csharp[](../../sample/AutoGen.BasicSamples/CodeSnippet/MistralAICodeSnippet.cs?name=streaming_chat)] \ No newline at end of file diff --git a/dotnet/website/articles/AutoGen-OpenAI-Overview.md b/dotnet/website/articles/AutoGen-OpenAI-Overview.md new file mode 100644 index 00000000000..f46cbcc455c --- /dev/null +++ b/dotnet/website/articles/AutoGen-OpenAI-Overview.md @@ -0,0 +1,17 @@ +## AutoGen.OpenAI Overview + +AutoGen.OpenAI provides the following agents over openai models: +- @AutoGen.OpenAI.OpenAIChatAgent: A slim wrapper agent over `OpenAIClient`. This agent only support `IMessage` message type. To support more message types like @AutoGen.Core.TextMessage, register the agent with @AutoGen.OpenAI.OpenAIChatRequestMessageConnector. +- @AutoGen.OpenAI.GPTAgent: An agent that build on top of @AutoGen.OpenAI.OpenAIChatAgent with more message types support like @AutoGen.Core.TextMessage, @AutoGen.Core.ImageMessage, @AutoGen.Core.MultiModalMessage and function call support. Essentially, it is equivalent to @AutoGen.OpenAI.OpenAIChatAgent with @AutoGen.Core.FunctionCallMiddleware and @AutoGen.OpenAI.OpenAIChatRequestMessageConnector registered. + +### Get start with AutoGen.OpenAI + +To get start with AutoGen.OpenAI, firstly, follow the [installation guide](Installation.md) to make sure you add the AutoGen feed correctly. Then add `AutoGen.OpenAI` package to your project file. + +```xml + + + +``` + + diff --git a/dotnet/website/articles/AutoGen-SemanticKernel-Overview.md b/dotnet/website/articles/AutoGen-SemanticKernel-Overview.md new file mode 100644 index 00000000000..581430b268d --- /dev/null +++ b/dotnet/website/articles/AutoGen-SemanticKernel-Overview.md @@ -0,0 +1,17 @@ +## AutoGen.SemanticKernel Overview + +AutoGen.SemanticKernel is a package that provides seamless integration with Semantic Kernel. It provides the following agent: +- @AutoGen.SemanticKernel.SemanticKernelAgent: A slim wrapper agent over `Kernel` that only support original `ChatMessageContent` type via `IMessage`. To support more AutoGen built-in message type, register the agent with @AutoGen.SemanticKernel.SemanticKernelChatMessageContentConnector. + +AutoGen.SemanticKernel also provides the following middleware: +- @AutoGen.SemanticKernel.SemanticKernelChatMessageContentConnector: A connector that convert the message from AutoGen built-in message types to `ChatMessageContent` and vice versa. At the current stage, it only supports conversation between @AutoGen.Core.TextMessage, @AutoGen.Core.ImageMessage and @AutoGen.Core.MultiModalMessage. Function call message type like @AutoGen.Core.ToolCallMessage and @AutoGen.Core.ToolCallResultMessage are not supported yet. + +### Get start with AutoGen.SemanticKernel + +To get start with AutoGen.SemanticKernel, firstly, follow the [installation guide](Installation.md) to make sure you add the AutoGen feed correctly. Then add `AutoGen.SemanticKernel` package to your project file. + +```xml + + + +``` \ No newline at end of file diff --git a/dotnet/website/articles/Built-in-messages.md b/dotnet/website/articles/Built-in-messages.md new file mode 100644 index 00000000000..2767091bd76 --- /dev/null +++ b/dotnet/website/articles/Built-in-messages.md @@ -0,0 +1,34 @@ +## An overview of built-in @AutoGen.Core.IMessage types + +Start from 0.0.9, AutoGen introduces the @AutoGen.Core.IMessage and @AutoGen.Core.IMessage`1 types to provide a unified message interface for different agents. The @AutoGen.Core.IMessage is a non-generic interface that represents a message. The @AutoGen.Core.IMessage`1 is a generic interface that represents a message with a specific `T` where `T` can be any type. + +Besides, AutoGen also provides a set of built-in message types that implement the @AutoGen.Core.IMessage and @AutoGen.Core.IMessage`1 interfaces. These built-in message types are designed to cover different types of messages as much as possilbe. The built-in message types include: + +> [!NOTE] +> The minimal requirement for an agent to be used as admin in @AutoGen.Core.GroupChat is to support @AutoGen.Core.TextMessage. + +- @AutoGen.Core.TextMessage: A message that contains a piece of text. +- @AutoGen.Core.ImageMessage: A message that contains an image. +- @AutoGen.Core.MultiModalMessage: A message that contains multiple modalities like text, image, etc. +- @AutoGen.Core.ToolCallMessage: A message that represents a function call request. +- @AutoGen.Core.ToolCallResultMessage: A message that represents a function call result. +- @AutoGen.Core.AggregateMessage`2: A message that represents an aggregate message that contains multiple sub-messages. This type of message is used by @AutoGen.Core.FunctionCallMiddleware to aggregate both @AutoGen.Core.ToolCallMessage and @AutoGen.Core.ToolCallResultMessage into a single message. +- @AutoGen.Core.MessageEnvelope`1: A message that represents an envelope that contains a message of any type. +- @AutoGen.Core.Message: The original message type before 0.0.9. This message type is reserved for backward compatibility. It is recommended to replace it with a more specific message type like @AutoGen.Core.TextMessage, @AutoGen.Core.ImageMessage, etc. + +### Streaming message support +AutoGen also introduces @AutoGen.Core.IStreamingMessage and @AutoGen.Core.IStreamingMessage`1 which are used in streaming call api. The following built-in message types implement the @AutoGen.Core.IStreamingMessage and @AutoGen.Core.IStreamingMessage`1 interfaces: + +> [!NOTE] +> All @AutoGen.Core.IMessage is also a @AutoGen.Core.IStreamingMessage. That means you can return an @AutoGen.Core.IMessage from a streaming call method. It's also recommended to return the final updated result instead of the last update as the last message in the streaming call method to indicate the end of the stream, which saves caller's effort of assembling the final result from multiple updates. +- @AutoGen.Core.TextMessageUpdate: A message that contains a piece of text update. +- @AutoGen.Core.ToolCallMessageUpdate: A message that contains a function call request update. + +#### Usage + +The below code snippet shows how to print a streaming update to console and update the final result on the caller side. +[!code-csharp[](../../sample/AutoGen.BasicSamples/CodeSnippet/BuildInMessageCodeSnippet.cs?name=StreamingCallCodeSnippet)] + +If the agent returns a final result instead of the last update as the last message in the streaming call method, the caller can directly use the final result without assembling the final result from multiple updates. + +[!code-csharp[](../../sample/AutoGen.BasicSamples/CodeSnippet/BuildInMessageCodeSnippet.cs?name=StreamingCallWithFinalMessage)] \ No newline at end of file diff --git a/dotnet/website/articles/Consume-LLM-server-from-LM-Studio.md b/dotnet/website/articles/Consume-LLM-server-from-LM-Studio.md new file mode 100644 index 00000000000..dff384a2678 --- /dev/null +++ b/dotnet/website/articles/Consume-LLM-server-from-LM-Studio.md @@ -0,0 +1,20 @@ +## Consume LLM server from LM Studio +You can use @AutoGen.LMStudio.LMStudioAgent from `AutoGen.LMStudio` package to consume openai-like API from LMStudio local server. + +### What's LM Studio +[LM Studio](https://lmstudio.ai/) is an app that allows you to deploy and inference hundreds of thousands of open-source language model on your local machine. It provides an in-app chat ui plus an openai-like API to interact with the language model programmatically. + +### Installation +- Install LM studio if you haven't done so. You can find the installation guide [here](https://lmstudio.ai/) +- Add `AutoGen.LMStudio` to your project. +```xml + + + +``` + +### Usage +The following code shows how to use `LMStudioAgent` to write a piece of C# code to calculate 100th of fibonacci. Before running the code, make sure you have local server from LM Studio running on `localhost:1234`. + +[!code-csharp[](../../sample/AutoGen.BasicSamples/Example08_LMStudio.cs?name=lmstudio_using_statements)] +[!code-csharp[](../../sample/AutoGen.BasicSamples/Example08_LMStudio.cs?name=lmstudio_example_1)] diff --git a/dotnet/website/articles/Create-a-user-proxy-agent.md b/dotnet/website/articles/Create-a-user-proxy-agent.md new file mode 100644 index 00000000000..44441ed3499 --- /dev/null +++ b/dotnet/website/articles/Create-a-user-proxy-agent.md @@ -0,0 +1,16 @@ +## UserProxyAgent + +[`UserProxyAgent`](../api/AutoGen.UserProxyAgent.yml) is a special type of agent that can be used to proxy user input to another agent or group of agents. It supports the following human input modes: +- `ALWAYS`: Always ask user for input. +- `NEVER`: Never ask user for input. In this mode, the agent will use the default response (if any) to respond to the message. Or using underlying LLM model to generate response if provided. +- `AUTO`: Only ask user for input when conversation is terminated by the other agent(s). Otherwise, use the default response (if any) to respond to the message. Or using underlying LLM model to generate response if provided. + +> [!TIP] +> You can also set up `humanInputMode` when creating `AssistantAgent` to enable/disable human input. `UserProxyAgent` is equivalent to `AssistantAgent` with `humanInputMode` set to `ALWAYS`. Similarly, `AssistantAgent` is equivalent to `UserProxyAgent` with `humanInputMode` set to `NEVER`. + +### Create a `UserProxyAgent` with `HumanInputMode` set to `ALWAYS` + +[!code-csharp[](../../sample/AutoGen.BasicSamples/CodeSnippet/UserProxyAgentCodeSnippet.cs?name=code_snippet_1)] + +When running the code, the user proxy agent will ask user for input and use the input as response. +![code output](../images/articles/CreateUserProxyAgent/image-1.png) \ No newline at end of file diff --git a/dotnet/website/articles/Create-an-agent.md b/dotnet/website/articles/Create-an-agent.md new file mode 100644 index 00000000000..1b56666daa1 --- /dev/null +++ b/dotnet/website/articles/Create-an-agent.md @@ -0,0 +1,11 @@ +## AssistantAgent + +[`AssistantAgent`](../api/AutoGen.AssistantAgent.yml) is a built-in agent in `AutoGen` that acts as an AI assistant. It uses LLM to generate response to user input. It also supports function call if the underlying LLM model supports it (e.g. `gpt-3.5-turbo-0613`). + +## Create an `AssistantAgent` using OpenAI model. + +[!code-csharp[](../../sample/AutoGen.BasicSamples/CodeSnippet/CreateAnAgent.cs?name=code_snippet_1)] + +## Create an `AssistantAgent` using Azure OpenAI model. + +[!code-csharp[](../../sample/AutoGen.BasicSamples/CodeSnippet/CreateAnAgent.cs?name=code_snippet_2)] diff --git a/dotnet/website/articles/Create-type-safe-function-call.md b/dotnet/website/articles/Create-type-safe-function-call.md new file mode 100644 index 00000000000..82bc5e84405 --- /dev/null +++ b/dotnet/website/articles/Create-type-safe-function-call.md @@ -0,0 +1,41 @@ +## Type-safe function call + +`AutoGen` provides a source generator to easness the trouble of manually craft function definition and function call wrapper from a function. To use this feature, simply add the `AutoGen.SourceGenerator` package to your project and decorate your function with @AutoGen.Core.FunctionAttribute. + +```bash +dotnet add package AutoGen.SourceGenerator +``` + +> [!NOTE] +> It's recommended to enable structural xml document support by setting `GenerateDocumentationFile` property to true in your project file. This allows source generator to leverage the documentation of the function when generating the function definition. + +```xml + + + true + +``` + +Then, create a `public partial` class to host the methods you want to use in AutoGen agents. The method has to be a `public` instance method and its return type must be `Task`. After the methods is defined, mark them with @AutoGen.FunctionAttribute attribute: + +> [!NOTE] +> A `public partial` class is required for the source generator to generate code. +> The method has to be a `public` instance method and its return type must be `Task`. +> Mark the method with @AutoGen.Core.FunctionAttribute attribute. + +Firstly, import the required namespaces: + +[!code-csharp[](../../sample/AutoGen.BasicSamples/CodeSnippet/TypeSafeFunctionCallCodeSnippet.cs?name=weather_report_using_statement)] + +Then, create a `WeatherReport` function and mark it with @AutoGen.Core.FunctionAttribute: + +[!code-csharp[](../../sample/AutoGen.BasicSamples/CodeSnippet/TypeSafeFunctionCallCodeSnippet.cs?name=weather_report)] + +The source generator will generate the @AutoGen.Core.FunctionContract and function call wrapper for `WeatherReport` in another partial class based on its signature and structural comments. The @AutoGen.Core.FunctionContract is introduced by [#1736](https://github.com/microsoft/autogen/pull/1736) and contains all the necessary metadata such as function name, parameters, and return type. It is LLM independent and can be used to generate openai function definition or semantic kernel function. The function call wrapper is a helper class that provides a type-safe way to call the function. + +> [!NOTE] +> If you are using VSCode as your editor, you may need to restart the editor to see the generated code. + +The following code shows how to generate openai function definition from the @AutoGen.Core.FunctionContract and call the function using the function call wrapper. + +[!code-csharp[](../../sample/AutoGen.BasicSamples/CodeSnippet/TypeSafeFunctionCallCodeSnippet.cs?name=weather_report_consume)] diff --git a/dotnet/website/articles/Create-your-own-agent.md b/dotnet/website/articles/Create-your-own-agent.md new file mode 100644 index 00000000000..a4548817c7f --- /dev/null +++ b/dotnet/website/articles/Create-your-own-agent.md @@ -0,0 +1 @@ +## Coming soon \ No newline at end of file diff --git a/dotnet/website/articles/Create-your-own-middleware.md b/dotnet/website/articles/Create-your-own-middleware.md new file mode 100644 index 00000000000..a4548817c7f --- /dev/null +++ b/dotnet/website/articles/Create-your-own-middleware.md @@ -0,0 +1 @@ +## Coming soon \ No newline at end of file diff --git a/dotnet/website/articles/Function-call-middleware.md b/dotnet/website/articles/Function-call-middleware.md new file mode 100644 index 00000000000..12c3c041535 --- /dev/null +++ b/dotnet/website/articles/Function-call-middleware.md @@ -0,0 +1 @@ +# Coming soon \ No newline at end of file diff --git a/dotnet/website/articles/Function-call-overview.md b/dotnet/website/articles/Function-call-overview.md new file mode 100644 index 00000000000..e8dfc54cd78 --- /dev/null +++ b/dotnet/website/articles/Function-call-overview.md @@ -0,0 +1,52 @@ +## Overview of function call + +In some LLM models, you can provide a list of function definitions to the model. The function definition is usually essentially an OpenAPI schema object which describes the function, its parameters and return value. And these function definitions tells the model what "functions" are available to be used to resolve the user's request. This feature greatly extend the capability of LLM models by enabling them to "execute" arbitrary function as long as it can be described as a function definition. + +Below is an example of a function definition for getting weather report for a city: + +> [!NOTE] +> To use function call, the underlying LLM model must support function call as well for the best experience. +> The model used in the example below is `gpt-3.5-turbo-0613`. +```json +{ + "name": "GetWeather", + "description": "Get the weather report for a city", + "parameters": { + "city": { + "type": "string", + "description": "The city name" + }, + "required": ["city"] + }, +} +``` + + + +When the model receives a message, it will intelligently decide whether to use function call or not based on the message received. If the model decides to use function call, it will generate a function call which can be used to invoke the actual function. The function call is a json object which contains the function name and its arguments. + +Below is an example of a function call object for getting weather report for Seattle: + +```json +{ + "name": "GetWeather", + "arguments": { + "city": "Seattle" + } +} +``` + +And when the function call is return to the caller, it can be used to invoke the actual function to get the weather report for Seattle. + +### Create type-safe function contract and function call wrapper use AutoGen.SourceGenerator +AutoGen provides a source generator to easness the trouble of manually craft function contract and function call wrapper from a function. To use this feature, simply add the `AutoGen.SourceGenerator` package to your project and decorate your function with `Function` attribute. + +For more information, please check out [Create type-safe function](Create-type-safe-function-call.md). + +### Use function call in an agent +AutoGen provides first-class support for function call in its agent story. Usually there are three ways to enable a function call in an agent. +- Pass function definitions when creating an agent. This only works if the agent supports pass function call from its constructor. +- Passing function definitions in @AutoGen.Core.GenerateReplyOptions when invoking an agent +- Register an agent with @AutoGen.Core.FunctionCallMiddleware to process and invoke function calls. + +For more information, please check out [Use function call in an agent](Use-function-call.md). \ No newline at end of file diff --git a/dotnet/website/articles/Group-chat-overview.md b/dotnet/website/articles/Group-chat-overview.md new file mode 100644 index 00000000000..6db7c64ab95 --- /dev/null +++ b/dotnet/website/articles/Group-chat-overview.md @@ -0,0 +1,8 @@ +@AutoGen.Core.IGroupChat is a fundamental feature in AutoGen. It provides a way to organize multiple agents under the same context and work together to resolve a given task. + +In AutoGen, there are two types of group chat: +- @AutoGen.Core.RoundRobinGroupChat : This group chat runs agents in a round-robin sequence. The chat history plus the most recent reply from the previous agent will be passed to the next agent. +- @AutoGen.Core.GroupChat : This group chat provides a more dynamic yet controlable way to determine the next speaker agent. You can either use a llm agent as group admin, or use a @AutoGen.Core.Graph, which is introduced by [this PR](https://github.com/microsoft/autogen/pull/1761), or both to determine the next speaker agent. + +> [!NOTE] +> In @AutoGen.Core.GroupChat, when only the group admin is used to determine the next speaker agent, it's recommented to use a more powerful llm model, such as `gpt-4` to ensure the best experience. \ No newline at end of file diff --git a/dotnet/website/articles/Group-chat.md b/dotnet/website/articles/Group-chat.md new file mode 100644 index 00000000000..058f4f2521d --- /dev/null +++ b/dotnet/website/articles/Group-chat.md @@ -0,0 +1,73 @@ +@AutoGen.Core.GroupChat invokes agents in a dynamic way. On one hand, It relies on its admin agent to intellegently determines the next speaker based on conversation context, and on the other hand, it also allows you to control the conversation flow by using a @AutoGen.Core.Graph. This makes it a more dynamic yet controlable way to determine the next speaker agent. You can use @AutoGen.Core.GroupChat to create a dynamic group chat with multiple agents working together to resolve a given task. + +> [!NOTE] +> In @AutoGen.Core.GroupChat, when only the group admin is used to determine the next speaker agent, it's recommented to use a more powerful llm model, such as `gpt-4` to ensure the best experience. + +## Use @AutoGen.Core.GroupChat to implement a code interpreter chat flow +The following example shows how to create a dynamic group chat with @AutoGen.Core.GroupChat. In this example, we will create a dynamic group chat with 4 agents: `admin`, `coder`, `reviewer` and `runner`. Each agent has its own role in the group chat: + +### Code interpreter group chat +- `admin`: create task for group to work on and terminate the conversation when task is completed. In this example, the task to resolve is to calculate the 39th Fibonacci number. +- `coder`: a dotnet coder who can write code to resolve tasks. +- `reviewer`: a dotnet code reviewer who can review code written by `coder`. In this example, `reviewer` will examine if the code written by `coder` follows the condition below: + - has only one csharp code block. + - use top-level statements. + - is dotnet code snippet. + - print the result of the code snippet to console. +- `runner`: a dotnet code runner who can run code written by `coder` and print the result. + +```mermaid +flowchart LR + subgraph Group Chat + B[Amin] + C[Coder] + D[Reviewer] + E[Runner] + end +``` + +> [!NOTE] +> The complete code of this example can be found in `Example07_Dynamic_GroupChat_Calculate_Fibonacci` + +### Create group chat + +The code below shows how to create a dynamic group chat with @AutoGen.Core.GroupChat. In this example, we will create a dynamic group chat with 4 agents: `admin`, `coder`, `reviewer` and `runner`. In this case we don't pass a workflow to the group chat, so the group chat will use driven by the admin agent. + +[!code-csharp[](../../sample/AutoGen.BasicSamples/Example07_Dynamic_GroupChat_Calculate_Fibonacci.cs?name=create_group_chat)] + +> [!TIP] +> You can set up initial context for the group chat using @AutoGen.Core.GroupChatExtension.SendIntroduction*. The initial context can help group admin orchestrates the conversation flow. + +Output: + +![GroupChat](../images/articles/DynamicGroupChat/dynamicChat.gif) + +### Below are break-down of how agents are created and their roles in the group chat. + +- Create admin agent + +The code below shows how to create `admin` agent. `admin` agent will create a task for group to work on and terminate the conversation when task is completed. + +[!code-csharp[](../../sample/AutoGen.BasicSamples/Example07_Dynamic_GroupChat_Calculate_Fibonacci.cs?name=create_admin)] + +- Create coder agent + +[!code-csharp[](../../sample/AutoGen.BasicSamples/Example07_Dynamic_GroupChat_Calculate_Fibonacci.cs?name=create_coder)] + +- Create reviewer agent + +The code below shows how to create `reviewer` agent. `reviewer` agent is a dotnet code reviewer who can review code written by `coder`. In this example, a `function` is used to examine if the code written by `coder` follows the condition. + +[!code-csharp[](../../sample/AutoGen.BasicSamples/Example07_Dynamic_GroupChat_Calculate_Fibonacci.cs?name=reviewer_function)] + +> [!TIP] +> You can use @AutoGen.Core.FunctionAttribute to generate type-safe function definition and function call wrapper for the function. For more information, please check out [Create type safe function call](./Create-type-safe-function-call.md). + +[!code-csharp[](../../sample/AutoGen.BasicSamples/Example07_Dynamic_GroupChat_Calculate_Fibonacci.cs?name=create_reviewer)] + +- Create runner agent + +> [!TIP] +> `AutoGen` provides a built-in support for running code snippet. For more information, please check out [Execute code snippet](./Run-dotnet-code.md). + +[!code-csharp[](../../sample/AutoGen.BasicSamples/Example07_Dynamic_GroupChat_Calculate_Fibonacci.cs?name=create_runner)] diff --git a/dotnet/website/articles/Installation.md b/dotnet/website/articles/Installation.md new file mode 100644 index 00000000000..59699a957d6 --- /dev/null +++ b/dotnet/website/articles/Installation.md @@ -0,0 +1,63 @@ +### Current version: + +[![NuGet version](https://badge.fury.io/nu/AutoGen.Core.svg)](https://badge.fury.io/nu/AutoGen.Core) + +AutoGen.Net provides the following packages, you can choose to install one or more of them based on your needs: + +- `AutoGen`: The one-in-all package. This package has dependencies over `AutoGen.Core`, `AutoGen.OpenAI`, `AutoGen.LMStudio`, `AutoGen.SemanticKernel` and `AutoGen.SourceGenerator`. +- `AutoGen.Core`: The core package, this package provides the abstraction for message type, agent and group chat. +- `AutoGen.OpenAI`: This package provides the integration agents over openai models. +- `AutoGen.Mistral`: This package provides the integration agents for Mistral.AI models. +- `AutoGen.LMStudio`: This package provides the integration agents from LM Studio. +- `AutoGen.SemanticKernel`: This package provides the integration agents over semantic kernel. +- `AutoGen.SourceGenerator`: This package carries a source generator that adds support for type-safe function definition generation. +- `AutoGen.DotnetInteractive`: This packages carries dotnet interactive support to execute dotnet code snippet. + +>[!Note] +> Help me choose +> - If you just want to install one package and enjoy the core features of AutoGen, choose `AutoGen`. +> - If you want to leverage AutoGen's abstraction only and want to avoid introducing any other dependencies, like `Azure.AI.OpenAI` or `Semantic Kernel`, choose `AutoGen.Core`. You will need to implement your own agent, but you can still use AutoGen core features like group chat, built-in message type, workflow and middleware. +>- If you want to use AutoGen with openai, choose `AutoGen.OpenAI`, similarly, choose `AutoGen.LMStudio` or `AutoGen.SemanticKernel` if you want to use agents from LM Studio or semantic kernel. +>- If you just want the type-safe source generation for function call and don't want any other features, which even include the AutoGen's abstraction, choose `AutoGen.SourceGenerator`. + +Then, install the package using the following command: + +```bash +dotnet add package AUTOGEN_PACKAGES +``` + +### Consume nightly build +To consume nightly build, you can add one of the following feeds to your `NuGet.config` or global nuget config: +- ![Static Badge](https://img.shields.io/badge/public-blue?style=flat) ![Static Badge](https://img.shields.io/badge/github-grey?style=flat): https://nuget.pkg.github.com/microsoft/index.json +- ![Static Badge](https://img.shields.io/badge/public-blue?style=flat) ![Static Badge](https://img.shields.io/badge/myget-grey?style=flat): https://www.myget.org/F/agentchat/api/v3/index.json +- ![Static Badge](https://img.shields.io/badge/internal-blue?style=flat) ![Static Badge](https://img.shields.io/badge/azure_devops-grey?style=flat) : https://devdiv.pkgs.visualstudio.com/DevDiv/_packaging/AutoGen/nuget/v3/index.json + +To add a local `NuGet.config`, create a file named `NuGet.config` in the root of your project and add the following content: +```xml + + + + + + + + + + + +``` + +To add the feed to your global nuget config. You can do this by running the following command in your terminal: +```bash +dotnet nuget add source FEED_URL --name AutoGen + +# dotnet-tools contains Microsoft.DotNet.Interactive.VisualStudio package, which is used by AutoGen.DotnetInteractive +dotnet nuget add source https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json --name dotnet-tools +``` + +Once you have added the feed, you can install the nightly-build package using the following command: +```bash +dotnet add package AUTOGEN_PACKAGES VERSION +``` + + diff --git a/dotnet/website/articles/Middleware-overview.md b/dotnet/website/articles/Middleware-overview.md new file mode 100644 index 00000000000..42355de33e6 --- /dev/null +++ b/dotnet/website/articles/Middleware-overview.md @@ -0,0 +1,27 @@ +`Middleware` is a key feature in AutoGen.Net that enables you to customize the behavior of @AutoGen.Core.IAgent.GenerateReplyAsync*. It's similar to the middleware concept in ASP.Net and is widely used in AutoGen.Net for various scenarios, such as function call support, converting message of different types, print message, gather user input, etc. + +Here are a few examples of how middleware is used in AutoGen.Net: +- @AutoGen.AssistantAgent is essentially an agent with @AutoGen.Core.FunctionCallMiddleware, @AutoGen.HumanInputMiddleware and default reply middleware. +- @AutoGen.OpenAI.GPTAgent is essentially an @AutoGen.OpenAI.OpenAIChatAgent with @AutoGen.Core.FunctionCallMiddleware and @AutoGen.OpenAI.OpenAIChatRequestMessageConnector. + +## Use middleware in an agent +To use middleware in an existing agent, you can either create a @AutoGen.Core.MiddlewareAgent on top of the original agent or register middleware functions to the original agent. + +### Create @AutoGen.Core.MiddlewareAgent on top of the original agent +[!code-csharp[](../../sample/AutoGen.BasicSamples/CodeSnippet/MiddlewareAgentCodeSnippet.cs?name=create_middleware_agent_with_original_agent)] + +### Register middleware functions to the original agent +[!code-csharp[](../../sample/AutoGen.BasicSamples/CodeSnippet/MiddlewareAgentCodeSnippet.cs?name=register_middleware_agent)] + +## Short-circuit the next agent +The example below shows how to short-circuit the inner agent + +[!code-csharp[](../../sample/AutoGen.BasicSamples/CodeSnippet/MiddlewareAgentCodeSnippet.cs?name=short_circuit_middleware_agent)] + +> [!Note] +> When multiple middleware functions are registered, the order of middleware functions is first registered, last invoked. + +## Streaming middleware +You can also modify the behavior of @AutoGen.Core.IStreamingAgent.GenerateStreamingReplyAsync* by registering streaming middleware to it. One example is @AutoGen.OpenAI.OpenAIChatRequestMessageConnector which converts `StreamingChatCompletionsUpdate` to one of `AutoGen.Core.TextMessageUpdate` or `AutoGen.Core.ToolCallMessageUpdate`. + +[!code-csharp[](../../sample/AutoGen.BasicSamples/CodeSnippet/MiddlewareAgentCodeSnippet.cs?name=register_streaming_middleware)] \ No newline at end of file diff --git a/dotnet/website/articles/MistralChatAgent-count-token-usage.md b/dotnet/website/articles/MistralChatAgent-count-token-usage.md new file mode 100644 index 00000000000..261845cf615 --- /dev/null +++ b/dotnet/website/articles/MistralChatAgent-count-token-usage.md @@ -0,0 +1,28 @@ +The following example shows how to create a `MistralAITokenCounterMiddleware` @AutoGen.Core.IMiddleware and count the token usage when chatting with @AutoGen.Mistral.MistralClientAgent. + +### Overview +To collect the token usage for the entire chat session, one easy solution is simply collect all the responses from agent and sum up the token usage for each response. To collect all the agent responses, we can create a middleware which simply saves all responses to a list and register it with the agent. To get the token usage information for each response, because in the example we are using @AutoGen.Mistral.MistralClientAgent, we can simply get the token usage from the response object. + +> [!NOTE] +> You can find the complete example in the [Example13_OpenAIAgent_JsonMode](https://github.com/microsoft/autogen/tree/main/dotnet/sample/AutoGen.BasicSamples/Example14_MistralClientAgent_TokenCount.cs). + +- Step 1: Adding using statement +[!code-csharp[](../../sample/AutoGen.BasicSamples/Example14_MistralClientAgent_TokenCount.cs?name=using_statements)] + +- Step 2: Create a `MistralAITokenCounterMiddleware` class which implements @AutoGen.Core.IMiddleware. This middleware will collect all the responses from the agent and sum up the token usage for each response. +[!code-csharp[](../../sample/AutoGen.BasicSamples/Example14_MistralClientAgent_TokenCount.cs?name=token_counter_middleware)] + +- Step 3: Create a `MistralClientAgent` +[!code-csharp[](../../sample/AutoGen.BasicSamples/Example14_MistralClientAgent_TokenCount.cs?name=create_mistral_client_agent)] + +- Step 4: Register the `MistralAITokenCounterMiddleware` with the `MistralClientAgent`. Note that the order of each middlewares matters. The token counter middleware needs to be registered before `mistralMessageConnector` because it collects response only when the responding message type is `IMessage` while the `mistralMessageConnector` will convert `IMessage` to one of @AutoGen.Core.TextMessage, @AutoGen.Core.ToolCallMessage or @AutoGen.Core.ToolCallResultMessage. +[!code-csharp[](../../sample/AutoGen.BasicSamples/Example14_MistralClientAgent_TokenCount.cs?name=register_middleware)] + +- Step 5: Chat with the `MistralClientAgent` and get the token usage information from the response object. +[!code-csharp[](../../sample/AutoGen.BasicSamples/Example14_MistralClientAgent_TokenCount.cs?name=chat_with_agent)] + +### Output +When running the example, the completion token count will be printed to the console. +```bash +Completion token count: 1408 # might be different based on the response +``` \ No newline at end of file diff --git a/dotnet/website/articles/MistralChatAgent-use-function-call.md b/dotnet/website/articles/MistralChatAgent-use-function-call.md new file mode 100644 index 00000000000..56ea0ffd08e --- /dev/null +++ b/dotnet/website/articles/MistralChatAgent-use-function-call.md @@ -0,0 +1,41 @@ +## Use tool in MistralChatAgent + +The following example shows how to enable tool support in @AutoGen.Mistral.MistralClientAgent by creating a `GetWeatherAsync` function and passing it to the agent. + +Firstly, you need to install the following packages: +```bash +dotnet add package AutoGen.Mistral +dotnet add package AutoGen.SourceGenerator +``` + +> [!Note] +> Tool support is only available in some mistral models. Please refer to the [link](https://docs.mistral.ai/capabilities/function_calling/#available-models) for tool call support in mistral models. + +> [!Note] +> The `AutoGen.SourceGenerator` package carries a source generator that adds support for type-safe function definition generation. For more information, please check out [Create type-safe function](./Create-type-safe-function-call.md). + +> [!NOTE] +> If you are using VSCode as your editor, you may need to restart the editor to see the generated code. + +Import the required namespace +[!code-csharp[](../../sample/AutoGen.BasicSamples/CodeSnippet/MistralAICodeSnippet.cs?name=using_statement)] + +Then define a public partial `MistralAgentFunction` class and `GetWeather` method. The `GetWeather` method is a simple function that returns the weather of a given location that marked with @AutoGen.Core.FunctionAttribute. Marking the class as `public partial` together with the @AutoGen.Core.FunctionAttribute attribute allows the source generator to generate the @AutoGen.Core.FunctionContract for the `GetWeather` method. + +[!code-csharp[](../../sample/AutoGen.BasicSamples/CodeSnippet/MistralAICodeSnippet.cs?name=weather_function)] + +Then create an @AutoGen.Mistral.MistralClientAgent and register it with @AutoGen.Mistral.Extension.MistralAgentExtension.RegisterMessageConnector* so it can support @AutoGen.Core.ToolCallMessage and @AutoGen.Core.ToolCallResultMessage. These message types are necessary to use @AutoGen.Core.FunctionCallMiddleware, which provides support for processing and invoking function calls. + +[!code-csharp[](../../sample/AutoGen.BasicSamples/CodeSnippet/MistralAICodeSnippet.cs?name=create_mistral_function_call_agent)] + +Then create an @AutoGen.Core.FunctionCallMiddleware with `GetWeather` function When creating the middleware, we also pass a `functionMap` object which means the function will be automatically invoked when the agent replies a `GetWeather` function call. + +[!code-csharp[](../../sample/AutoGen.BasicSamples/CodeSnippet/MistralAICodeSnippet.cs?name=create_get_weather_function_call_middleware)] + +After the function call middleware is created, register it with the agent so the `GetWeather` function will be passed to agent during chat completion. + +[!code-csharp[](../../sample/AutoGen.BasicSamples/CodeSnippet/MistralAICodeSnippet.cs?name=register_function_call_middleware)] + +Finally, you can chat with the @AutoGen.Mistral.MistralClientAgent about weather! The agent will automatically invoke the `GetWeather` function to "get" the weather information and return the result. + +[!code-csharp[](../../sample/AutoGen.BasicSamples/CodeSnippet/MistralAICodeSnippet.cs?name=send_message_with_function_call)] \ No newline at end of file diff --git a/dotnet/website/articles/OpenAIChatAgent-connect-to-third-party-api.md b/dotnet/website/articles/OpenAIChatAgent-connect-to-third-party-api.md new file mode 100644 index 00000000000..8321fc87a5c --- /dev/null +++ b/dotnet/website/articles/OpenAIChatAgent-connect-to-third-party-api.md @@ -0,0 +1,50 @@ +The following example shows how to connect to third-party OpenAI API using @AutoGen.OpenAI.OpenAIChatAgent. + +> [!NOTE] +> You can find the complete code of this example in [Example16_OpenAIChatAgent_ConnectToThirdPartyBackend](https://github.com/microsoft/autogen/tree/main/dotnet/sample/AutoGen.BasicSamples/Example16_OpenAIChatAgent_ConnectToThirdPartyBackend.cs). + +## Overview +A lot of LLM applications/platforms support spinning up a chat server that is compatible with OpenAI API, such as LM Studio, Ollama, Mistral etc. This means that you can connect to these servers using the @AutoGen.OpenAI.OpenAIChatAgent. + +> [!NOTE] +> Some platforms might not support all the features of OpenAI API. For example, Ollama does not support `function call` when using it's openai API according to its [document](https://github.com/ollama/ollama/blob/main/docs/openai.md#v1chatcompletions) (as of 2024/05/07). +> That means some of the features of OpenAI API might not work as expected when using these platforms with the @AutoGen.OpenAI.OpenAIChatAgent. +> Please refer to the platform's documentation for more information. + +## Prerequisites +- Install the following packages: +```bash +dotnet add package AutoGen.OpenAI --version AUTOGEN_VERSION +``` + +- Spin up a chat server that is compatible with OpenAI API. +The following example uses Ollama as the chat server, and llama3 as the llm model. +```bash +ollama serve +``` + +## Steps +- Import the required namespaces: +[!code-csharp[](../../sample/AutoGen.BasicSamples/Example16_OpenAIChatAgent_ConnectToThirdPartyBackend.cs?name=using_statement)] + +- Create a `CustomHttpClientHandler` class. + +The `CustomHttpClientHandler` class is used to customize the HttpClientHandler. In this example, we override the `SendAsync` method to redirect the request to local Ollama server, which is running on `http://localhost:11434`. + +[!code-csharp[](../../sample/AutoGen.BasicSamples/Example16_OpenAIChatAgent_ConnectToThirdPartyBackend.cs?name=CustomHttpClientHandler)] + +- Create an `OpenAIChatAgent` instance and connect to the third-party API. + +Then create an @AutoGen.OpenAI.OpenAIChatAgent instance and connect to the OpenAI API from Ollama. You can customize the transport behavior of `OpenAIClient` by passing a customized `HttpClientTransport` instance. In the customized `HttpClientTransport` instance, we pass the `CustomHttpClientHandler` we just created which redirects all openai chat requests to the local Ollama server. + +[!code-csharp[](../../sample/AutoGen.BasicSamples/Example16_OpenAIChatAgent_ConnectToThirdPartyBackend.cs?name=create_agent)] + +- Chat with the `OpenAIChatAgent`. +Finally, you can start chatting with the agent. In this example, we send a coding question to the agent and get the response. + +[!code-csharp[](../../sample/AutoGen.BasicSamples/Example16_OpenAIChatAgent_ConnectToThirdPartyBackend.cs?name=send_message)] + +## Sample Output +The following is the sample output of the code snippet above: + +![output](../images/articles/ConnectTo3PartyOpenAI/output.gif) \ No newline at end of file diff --git a/dotnet/website/articles/OpenAIChatAgent-simple-chat.md b/dotnet/website/articles/OpenAIChatAgent-simple-chat.md new file mode 100644 index 00000000000..867aff24af9 --- /dev/null +++ b/dotnet/website/articles/OpenAIChatAgent-simple-chat.md @@ -0,0 +1,11 @@ +The following example shows how to create an @AutoGen.OpenAI.OpenAIChatAgent and chat with it. + +Firsly, import the required namespaces: +[!code-csharp[](../../sample/AutoGen.BasicSamples/CodeSnippet/OpenAICodeSnippet.cs?name=using_statement)] + +Then, create an @AutoGen.OpenAI.OpenAIChatAgent and chat with it: +[!code-csharp[](../../sample/AutoGen.BasicSamples/CodeSnippet/OpenAICodeSnippet.cs?name=create_openai_chat_agent)] + +@AutoGen.OpenAI.OpenAIChatAgent also supports streaming chat via @AutoGen.Core.IAgent.GenerateStreamingReplyAsync*. + +[!code-csharp[](../../sample/AutoGen.BasicSamples/CodeSnippet/OpenAICodeSnippet.cs?name=create_openai_chat_agent_streaming)] \ No newline at end of file diff --git a/dotnet/website/articles/OpenAIChatAgent-support-more-messages.md b/dotnet/website/articles/OpenAIChatAgent-support-more-messages.md new file mode 100644 index 00000000000..af6e60682b2 --- /dev/null +++ b/dotnet/website/articles/OpenAIChatAgent-support-more-messages.md @@ -0,0 +1,6 @@ +By default, @AutoGen.OpenAI.OpenAIChatAgent only supports the @AutoGen.Core.IMessage type where `T` is original request or response message from `Azure.AI.OpenAI`. To support more AutoGen built-in message types like @AutoGen.Core.TextMessage, @AutoGen.Core.ImageMessage, @AutoGen.Core.MultiModalMessage and so on, you can register the agent with @AutoGen.OpenAI.OpenAIChatRequestMessageConnector. The @AutoGen.OpenAI.OpenAIChatRequestMessageConnector will convert the message from AutoGen built-in message types to `Azure.AI.OpenAI.ChatRequestMessage` and vice versa. + +import the required namespaces: +[!code-csharp[](../../sample/AutoGen.BasicSamples/CodeSnippet/OpenAICodeSnippet.cs?name=using_statement)] + +[!code-csharp[](../../sample/AutoGen.BasicSamples/CodeSnippet/OpenAICodeSnippet.cs?name=register_openai_chat_message_connector)] \ No newline at end of file diff --git a/dotnet/website/articles/OpenAIChatAgent-use-function-call.md b/dotnet/website/articles/OpenAIChatAgent-use-function-call.md new file mode 100644 index 00000000000..da12ae9e90a --- /dev/null +++ b/dotnet/website/articles/OpenAIChatAgent-use-function-call.md @@ -0,0 +1,33 @@ +The following example shows how to create a `GetWeatherAsync` function and pass it to @AutoGen.OpenAI.OpenAIChatAgent. + +Firstly, you need to install the following packages: +```xml + + + + +``` + +> [!Note] +> The `AutoGen.SourceGenerator` package carries a source generator that adds support for type-safe function definition generation. For more information, please check out [Create type-safe function](./Create-type-safe-function-call.md). + +> [!NOTE] +> If you are using VSCode as your editor, you may need to restart the editor to see the generated code. + +Firstly, import the required namespaces: +[!code-csharp[](../../sample/AutoGen.BasicSamples/CodeSnippet/OpenAICodeSnippet.cs?name=using_statement)] + +Then, define a public partial class: `Function` with `GetWeather` method +[!code-csharp[](../../sample/AutoGen.BasicSamples/CodeSnippet/OpenAICodeSnippet.cs?name=weather_function)] + +Then, create an @AutoGen.OpenAI.OpenAIChatAgent and register it with @AutoGen.OpenAI.OpenAIChatRequestMessageConnector so it can support @AutoGen.Core.ToolCallMessage and @AutoGen.Core.ToolCallResultMessage. These message types are necessary to use @AutoGen.Core.FunctionCallMiddleware, which provides support for processing and invoking function calls. + +[!code-csharp[](../../sample/AutoGen.BasicSamples/CodeSnippet/OpenAICodeSnippet.cs?name=openai_chat_agent_get_weather_function_call)] + +Then, create an @AutoGen.Core.FunctionCallMiddleware with `GetWeather` function and register it with the agent above. When creating the middleware, we also pass a `functionMap` to @AutoGen.Core.FunctionCallMiddleware, which means the function will be automatically invoked when the agent replies a `GetWeather` function call. + +[!code-csharp[](../../sample/AutoGen.BasicSamples/CodeSnippet/OpenAICodeSnippet.cs?name=create_function_call_middleware)] + +Finally, you can chat with the @AutoGen.OpenAI.OpenAIChatAgent and invoke the `GetWeather` function. + +[!code-csharp[](../../sample/AutoGen.BasicSamples/CodeSnippet/OpenAICodeSnippet.cs?name=chat_agent_send_function_call)] \ No newline at end of file diff --git a/dotnet/website/articles/OpenAIChatAgent-use-json-mode.md b/dotnet/website/articles/OpenAIChatAgent-use-json-mode.md new file mode 100644 index 00000000000..a822cb04633 --- /dev/null +++ b/dotnet/website/articles/OpenAIChatAgent-use-json-mode.md @@ -0,0 +1,31 @@ +The following example shows how to enable JSON mode in @AutoGen.OpenAI.OpenAIChatAgent. + +## What is JSON mode? +JSON mode is a new feature in OpenAI which allows you to instruct model to always respond with a valid JSON object. This is useful when you want to constrain the model output to JSON format only. + +> [!NOTE] +> Currently, JOSN mode is only supported by `gpt-4-turbo-preview` and `gpt-3.5-turbo-0125`. For more information (and limitations) about JSON mode, please visit [OpenAI API documentation](https://platform.openai.com/docs/guides/text-generation/json-mode). + +## How to enable JSON mode in OpenAIChatAgent. + +> [!NOTE] +> You can find the complete example in the [Example13_OpenAIAgent_JsonMode](https://github.com/microsoft/autogen/tree/main/dotnet/sample/AutoGen.BasicSamples/Example13_OpenAIAgent_JsonMode.cs). + +To enable JSON mode for @AutoGen.OpenAI.OpenAIChatAgent, set `responseFormat` to `ChatCompletionsResponseFormat.JsonObject` when creating the agent. Note that when enabling JSON mode, you also need to instruct the agent to output JSON format in its system message. + +[!code-csharp[](../../sample/AutoGen.BasicSamples/Example13_OpenAIAgent_JsonMode.cs?name=create_agent)] + +After enabling JSON mode, the `openAIClientAgent` will always respond in JSON format when it receives a message. + +[!code-csharp[](../../sample/AutoGen.BasicSamples/Example13_OpenAIAgent_JsonMode.cs?name=chat_with_agent)] + +When running the example, the output from `openAIClientAgent` will be a valid JSON object which can be parsed as `Person` class defined below. Note that in the output, the `address` field is missing because the address information is not provided in user input. + +[!code-csharp[](../../sample/AutoGen.BasicSamples/Example13_OpenAIAgent_JsonMode.cs?name=person_class)] + +The output will be: +```bash +Name: John +Age: 25 +Done +``` \ No newline at end of file diff --git a/dotnet/website/articles/Print-message-middleware.md b/dotnet/website/articles/Print-message-middleware.md new file mode 100644 index 00000000000..b0115970d77 --- /dev/null +++ b/dotnet/website/articles/Print-message-middleware.md @@ -0,0 +1,27 @@ +@AutoGen.Core.PrintMessageMiddleware is a built-in @AutoGen.Core.IMiddleware that pretty print @AutoGen.Core.IMessage to console. + +> [!NOTE] +> @AutoGen.Core.PrintMessageMiddleware support the following @AutoGen.Core.IMessage types: +> - @AutoGen.Core.TextMessage +> - @AutoGen.Core.MultiModalMessage +> - @AutoGen.Core.ToolCallMessage +> - @AutoGen.Core.ToolCallResultMessage +> - @AutoGen.Core.Message +> - (streaming) @AutoGen.Core.TextMessageUpdate +> - (streaming) @AutoGen.Core.ToolCallMessageUpdate + +## Use @AutoGen.Core.PrintMessageMiddleware in an agent +You can use @AutoGen.Core.PrintMessageMiddlewareExtension.RegisterPrintMessage* to register the @AutoGen.Core.PrintMessageMiddleware to an agent. + +[!code-csharp[](../../sample/AutoGen.BasicSamples/CodeSnippet/PrintMessageMiddlewareCodeSnippet.cs?name=PrintMessageMiddleware)] + +@AutoGen.Core.PrintMessageMiddlewareExtension.RegisterPrintMessage* will format the message and print it to console +![image](../images/articles/PrintMessageMiddleware/printMessage.png) + +## Streaming message support + +@AutoGen.Core.PrintMessageMiddleware also supports streaming message types like @AutoGen.Core.TextMessageUpdate and @AutoGen.Core.ToolCallMessageUpdate. If you register @AutoGen.Core.PrintMessageMiddleware to a @AutoGen.Core.IStreamingAgent, it will format the streaming message and print it to console if the message is of supported type. + +[!code-csharp[](../../sample/AutoGen.BasicSamples/CodeSnippet/PrintMessageMiddlewareCodeSnippet.cs?name=print_message_streaming)] + +![image](../images/articles/PrintMessageMiddleware/streamingoutput.gif) diff --git a/dotnet/website/articles/Roundrobin-chat.md b/dotnet/website/articles/Roundrobin-chat.md new file mode 100644 index 00000000000..20fd19b4d79 --- /dev/null +++ b/dotnet/website/articles/Roundrobin-chat.md @@ -0,0 +1,33 @@ +@AutoGen.Core.RoundRobinGroupChat is a group chat that invokes agents in a round-robin order. It's useful when you want to call multiple agents in a fixed sequence. For example, asking search agent to retrieve related information followed by a summarization agent to summarize the information. Beside, it also used by @AutoGen.Core.AgentExtension.SendAsync(AutoGen.Core.IAgent,AutoGen.Core.IAgent,System.String,System.Collections.Generic.IEnumerable{AutoGen.Core.IMessage},System.Int32,System.Threading.CancellationToken) in two agent chat. + +### Use @AutoGen.Core.RoundRobinGroupChat to implement a search-summarize chat flow + +```mermaid +flowchart LR + A[User] -->|Ask a question| B[Search Agent] + B -->|Retrieve information| C[Summarization Agent] + C -->|Summarize result| A[User] +``` + +> [!NOTE] +> Complete code can be found in [Example11_Sequential_GroupChat_Example](https://github.com/microsoft/autogen/blob/dotnet/dotnet/sample/AutoGen.BasicSamples/Example11_Sequential_GroupChat_Example.cs); + +Step 1: Add required using statements + +[!code-csharp[](../../sample/AutoGen.BasicSamples/Example11_Sequential_GroupChat_Example.cs?name=using_statement)] + +Step 2: Create a `bingSearch` agent using @AutoGen.SemanticKernel.SemanticKernelAgent + +[!code-csharp[](../../sample/AutoGen.BasicSamples/Example11_Sequential_GroupChat_Example.cs?name=CreateBingSearchAgent)] + +Step 3: Create a `summarization` agent using @AutoGen.SemanticKernel.SemanticKernelAgent + +[!code-csharp[](../../sample/AutoGen.BasicSamples/Example11_Sequential_GroupChat_Example.cs?name=CreateSummarizerAgent)] + +Step 4: Create a @AutoGen.Core.RoundRobinGroupChat and add `bingSearch` and `summarization` agents to it + +[!code-csharp[](../../sample/AutoGen.BasicSamples/Example11_Sequential_GroupChat_Example.cs?name=Sequential_GroupChat_Example)] + +Output: + +![Searcher-Summarizer](../images/articles/SequentialGroupChat/SearcherSummarizer.gif) \ No newline at end of file diff --git a/dotnet/website/articles/Run-dotnet-code.md b/dotnet/website/articles/Run-dotnet-code.md new file mode 100644 index 00000000000..e3d8fa78a0b --- /dev/null +++ b/dotnet/website/articles/Run-dotnet-code.md @@ -0,0 +1,32 @@ +`AutoGen` provides a built-in feature to run code snippet from agent response. Currently the following languages are supported: +- dotnet + +More languages will be supported in the future. + +## What is a code snippet? +A code snippet in agent response is a code block with a language identifier. For example: + +[!code-csharp[](../../sample/AutoGen.BasicSamples/CodeSnippet/RunCodeSnippetCodeSnippet.cs?name=code_snippet_1_3)] + +## Why running code snippet is useful? +The ability of running code snippet can greatly extend the ability of an agent. Because it enables agent to resolve tasks by writing code and run it, which is much more powerful than just returning a text response. + +For example, in data analysis scenario, agent can resolve tasks like "What is the average of the sales amount of the last 7 days?" by firstly write a code snippet to query the sales amount of the last 7 days, then calculate the average and then run the code snippet to get the result. + +> [!WARNING] +> Running arbitrary code snippet from agent response could bring risks to your system. Using this feature with caution. + +## How to run dotnet code snippet? +The built-in feature of running dotnet code snippet is provided by [dotnet-interactive](https://github.com/dotnet/interactive). To run dotnet code snippet, you need to install the following package to your project, which provides the intergraion with dotnet-interactive: + +```xml + +``` + +Then you can use @AutoGen.DotnetInteractive.AgentExtension.RegisterDotnetCodeBlockExectionHook(AutoGen.IAgent,InteractiveService,System.String,System.String) to register a `reply hook` to run dotnet code snippet. The hook will check if a csharp code snippet is present in the most recent message from history, and run the code snippet if it is present. + +The following code snippet shows how to register a dotnet code snippet execution hook: + +[!code-csharp[](../../sample/AutoGen.BasicSamples/CodeSnippet/RunCodeSnippetCodeSnippet.cs?name=code_snippet_0_1)] +[!code-csharp[](../../sample/AutoGen.BasicSamples/CodeSnippet/RunCodeSnippetCodeSnippet.cs?name=code_snippet_1_1)] +[!code-csharp[](../../sample/AutoGen.BasicSamples/CodeSnippet/RunCodeSnippetCodeSnippet.cs?name=code_snippet_1_2)] diff --git a/dotnet/website/articles/SemanticKernelAgent-simple-chat.md b/dotnet/website/articles/SemanticKernelAgent-simple-chat.md new file mode 100644 index 00000000000..9b16ceb4e18 --- /dev/null +++ b/dotnet/website/articles/SemanticKernelAgent-simple-chat.md @@ -0,0 +1,9 @@ +You can chat with @AutoGen.SemanticKernel.SemanticKernelAgent using both streaming and non-streaming methods and use native `ChatMessageContent` type via `IMessage`. + +The following example shows how to create an @AutoGen.SemanticKernel.SemanticKernelAgent and chat with it using non-streaming method: + +[!code-csharp[](../../sample/AutoGen.BasicSamples/CodeSnippet/SemanticKernelCodeSnippet.cs?name=create_semantic_kernel_agent)] + +@AutoGen.SemanticKernel.SemanticKernelAgent also supports streaming chat via @AutoGen.Core.IStreamingAgent.GenerateStreamingReplyAsync*. + +[!code-csharp[](../../sample/AutoGen.BasicSamples/CodeSnippet/SemanticKernelCodeSnippet.cs?name=create_semantic_kernel_agent_streaming)] diff --git a/dotnet/website/articles/SemanticKernelAgent-support-more-messages.md b/dotnet/website/articles/SemanticKernelAgent-support-more-messages.md new file mode 100644 index 00000000000..3dfcf5b0d38 --- /dev/null +++ b/dotnet/website/articles/SemanticKernelAgent-support-more-messages.md @@ -0,0 +1,10 @@ +@AutoGen.SemanticKernel.SemanticKernelAgent only supports the original `ChatMessageContent` type via `IMessage`. To support more AutoGen built-in message types like @AutoGen.Core.TextMessage, @AutoGen.Core.ImageMessage, @AutoGen.Core.MultiModalMessage, you can register the agent with @AutoGen.SemanticKernel.SemanticKernelChatMessageContentConnector. The @AutoGen.SemanticKernel.SemanticKernelChatMessageContentConnector will convert the message from AutoGen built-in message types to `ChatMessageContent` and vice versa. +> [!NOTE] +> At the current stage, @AutoGen.SemanticKernel.SemanticKernelChatMessageContentConnector only supports conversation for the followng built-in @AutoGen.Core.IMessage +> - @AutoGen.Core.TextMessage +> - @AutoGen.Core.ImageMessage +> - @AutoGen.Core.MultiModalMessage +> +> Function call message type like @AutoGen.Core.ToolCallMessage and @AutoGen.Core.ToolCallResultMessage are not supported yet. + +[!code-csharp[](../../sample/AutoGen.BasicSamples/CodeSnippet/SemanticKernelCodeSnippet.cs?name=register_semantic_kernel_chat_message_content_connector)] \ No newline at end of file diff --git a/dotnet/website/articles/Two-agent-chat.md b/dotnet/website/articles/Two-agent-chat.md new file mode 100644 index 00000000000..2fe5f8401e1 --- /dev/null +++ b/dotnet/website/articles/Two-agent-chat.md @@ -0,0 +1,19 @@ +In `AutoGen`, you can start a conversation between two agents using @AutoGen.Core.AgentExtension.InitiateChatAsync* or one of @AutoGen.Core.AgentExtension.SendAsync* APIs. When conversation starts, the sender agent will firstly send a message to receiver agent, then receiver agent will generate a reply and send it back to sender agent. This process will repeat until either one of the agent sends a termination message or the maximum number of turns is reached. + +> [!NOTE] +> A termination message is an @AutoGen.Core.IMessage which content contains the keyword: @AutoGen.Core.GroupChatExtension.TERMINATE. To determine if a message is a terminate message, you can use @AutoGen.Core.GroupChatExtension.IsGroupChatTerminateMessage*. + +## A basic example + +The following example shows how to start a conversation between the teacher agent and student agent, where the student agent starts the conversation by asking teacher to create math questions. + +> [!TIP] +> You can use @AutoGen.Core.PrintMessageMiddlewareExtension.RegisterPrintMessage* to pretty print the message replied by the agent. + +> [!NOTE] +> The conversation is terminated when teacher agent sends a message containing the keyword: @AutoGen.Core.GroupChatExtension.TERMINATE. + +> [!NOTE] +> The teacher agent uses @AutoGen.Core.MiddlewareExtension.RegisterPostProcess* to register a post process function which returns a hard-coded termination message when a certain condition is met. Comparing with putting the @AutoGen.Core.GroupChatExtension.TERMINATE keyword in the prompt, this approach is more robust especially when a weaker LLM model is used. + +[!code-csharp[](../../sample/AutoGen.BasicSamples/Example02_TwoAgent_MathChat.cs?name=code_snippet_1)] diff --git a/dotnet/website/articles/Use-function-call.md b/dotnet/website/articles/Use-function-call.md new file mode 100644 index 00000000000..8c0f172e7da --- /dev/null +++ b/dotnet/website/articles/Use-function-call.md @@ -0,0 +1,43 @@ +## Use function call in AutoGen agent + +Typically, there are three ways to pass a function definition to an agent to enable function call: +- Pass function definitions when creating an agent. This only works if the agent supports pass function call from its constructor. +- Passing function definitions in @AutoGen.Core.GenerateReplyOptions when invoking an agent +- Register an agent with @AutoGen.Core.FunctionCallMiddleware to process and invoke function calls. + +> [!NOTE] +> To use function call, the underlying LLM model must support function call as well for the best experience. If the model does not support function call, it's likely that the function call will be ignored and the model will reply with a normal response even if a function call is passed to it. + +## Pass function definitions when creating an agent +In some agents like @AutoGen.AssistantAgent or @AutoGen.OpenAI.GPTAgent, you can pass function definitions when creating the agent + +Suppose the `TypeSafeFunctionCall` is defined in the following code snippet: +[!code-csharp[TypeSafeFunctionCall](../../sample/AutoGen.BasicSamples/CodeSnippet/TypeSafeFunctionCallCodeSnippet.cs?name=weather_report)] + +You can then pass the `WeatherReport` to the agent when creating it: +[!code-csharp[assistant agent](../../sample/AutoGen.BasicSamples/CodeSnippet/FunctionCallCodeSnippet.cs?name=code_snippet_4)] + +## Passing function definitions in @AutoGen.Core.GenerateReplyOptions when invoking an agent +You can also pass function definitions in @AutoGen.Core.GenerateReplyOptions when invoking an agent. This is useful when you want to override the function definitions passed to the agent when creating it. + +[!code-csharp[assistant agent](../../sample/AutoGen.BasicSamples/CodeSnippet/FunctionCallCodeSnippet.cs?name=overrider_function_contract)] + +## Register an agent with @AutoGen.Core.FunctionCallMiddleware to process and invoke function calls +You can also register an agent with @AutoGen.Core.FunctionCallMiddleware to process and invoke function calls. This is useful when you want to process and invoke function calls in a more flexible way. + +[!code-csharp[assistant agent](../../sample/AutoGen.BasicSamples/CodeSnippet/FunctionCallCodeSnippet.cs?name=register_function_call_middleware)] + +## Invoke function call inside an agent +To invoke a function instead of returning the function call object, you can pass its function call wrapper to the agent via `functionMap`. + +You can then pass the `WeatherReportWrapper` to the agent via `functionMap`: +[!code-csharp[](../../sample/AutoGen.BasicSamples/CodeSnippet/FunctionCallCodeSnippet.cs?name=code_snippet_6)] + +When a function call object is returned, the agent will invoke the function and uses the return value as response rather than returning the function call object. + +[!code-csharp[](../../sample/AutoGen.BasicSamples/CodeSnippet/FunctionCallCodeSnippet.cs?name=code_snippet_6_1)] + +## Invoke function call by another agent +You can also use another agent to invoke the function call from one agent. This is a useful pattern in two-agent chat, where one agent is used as a function proxy to invoke the function call from another agent. Once the function call is invoked, the result can be returned to the original agent for further processing. + +[!code-csharp[](../../sample/AutoGen.BasicSamples/CodeSnippet/FunctionCallCodeSnippet.cs?name=two_agent_weather_chat)] \ No newline at end of file diff --git a/dotnet/website/articles/Use-graph-in-group-chat.md b/dotnet/website/articles/Use-graph-in-group-chat.md new file mode 100644 index 00000000000..1cc97e50fe6 --- /dev/null +++ b/dotnet/website/articles/Use-graph-in-group-chat.md @@ -0,0 +1,25 @@ +Sometimes, you may want to add more control on how the next agent is selected in a @AutoGen.Core.GroupChat based on the task you want to resolve. For example, in the previous [code writing example](./Group-chat.md), the original code interpreter workflow can be improved by the following diagram because it's not necessary for `admin` to directly talk to `reviewer`, nor it's necessary for `coder` to talk to `runner`. + +```mermaid +flowchart TD + A[Admin] -->|Ask coder to write code| B[Coder] + B -->|Ask Reviewer to review code| C[Reviewer] + C -->|Ask Runner to run code| D[Runner] + D -->|Send result if succeed| A[Admin] + D -->|Ask coder to fix if failed| B[Coder] + C -->|Ask coder to fix if not approved| B[Coder] +``` + +By having @AutoGen.Core.GroupChat to follow a specific graph flow, we can bring prior knowledge to group chat and make the conversation more efficient and robust. This is where @AutoGen.Core.Graph comes in. + +### Create a graph +The following code shows how to create a graph that represents the diagram above. The graph doesn't need to be a finite state machine where each state can only have one legitimate next state. Instead, it can be a directed graph where each state can have multiple legitimate next states. And if there are multiple legitimate next states, the `admin` agent of @AutoGen.Core.GroupChat will decide which one to go based on the conversation context. + +> [!TIP] +> @AutoGen.Core.Graph supports conditional transitions. To create a conditional transition, you can pass a lambda function to `canTransitionAsync` when creating a @AutoGen.Core.Transition. The lambda function should return a boolean value indicating if the transition can be taken. + +[!code-csharp[](../../sample/AutoGen.BasicSamples/Example07_Dynamic_GroupChat_Calculate_Fibonacci.cs?name=create_workflow)] + +Once the graph is created, you can pass it to the group chat. The group chat will then use the graph along with admin agent to orchestrate the conversation flow. + +[!code-csharp[](../../sample/AutoGen.BasicSamples/Example07_Dynamic_GroupChat_Calculate_Fibonacci.cs?name=create_group_chat_with_workflow)] \ No newline at end of file diff --git a/dotnet/website/articles/getting-start.md b/dotnet/website/articles/getting-start.md new file mode 100644 index 00000000000..53cc7c9758f --- /dev/null +++ b/dotnet/website/articles/getting-start.md @@ -0,0 +1,24 @@ +### Get start with AutoGen for dotnet +[![dotnet-ci](https://github.com/microsoft/autogen/actions/workflows/dotnet-build.yml/badge.svg)](https://github.com/microsoft/autogen/actions/workflows/dotnet-build.yml) +[![Discord](https://img.shields.io/discord/1153072414184452236?logo=discord&style=flat)](https://discord.gg/pAbnFJrkgZ) +[![NuGet version](https://badge.fury.io/nu/AutoGen.Core.svg)](https://badge.fury.io/nu/AutoGen.Core) + +Firstly, add `AutoGen` package to your project. + +```bash +dotnet add package AutoGen +``` + +> [!NOTE] +> For more information about installing packages, please check out the [installation guide](Installation.md). + +Then you can start with the following code snippet to create a conversable agent and chat with it. + +[!code-csharp[](../../sample/AutoGen.BasicSamples/CodeSnippet/GetStartCodeSnippet.cs?name=snippet_GetStartCodeSnippet)] +[!code-csharp[](../../sample/AutoGen.BasicSamples/CodeSnippet/GetStartCodeSnippet.cs?name=code_snippet_1)] + +### Examples +You can find more examples under the [sample project](https://github.com/microsoft/autogen/tree/dotnet/dotnet/sample/AutoGen.BasicSamples). + +### Report a bug or request a feature +You can report a bug or request a feature by creating a new issue in the [github issue](https://github.com/microsoft/autogen/issues) and specifying label the label "donet" diff --git a/dotnet/website/articles/toc.yml b/dotnet/website/articles/toc.yml new file mode 100644 index 00000000000..325578ad41a --- /dev/null +++ b/dotnet/website/articles/toc.yml @@ -0,0 +1,93 @@ +- name: Getting start + items: + - name: Installation + href: Installation.md + - name: agent + items: + - name: agent overview + href: Agent-overview.md + - name: assistant agent + href: Create-an-agent.md + - name: user proxy agent + href: Create-a-user-proxy-agent.md + - name: Chat with an agent using user proxy agent + href: Two-agent-chat.md + # - name: Create your own agent + # href: Create-your-own-agent.md + - name: built-in messages + href: Built-in-messages.md + - name: function call + items: + - name: Function call overview + href: Function-call-overview.md + - name: Create type-safe function call using AutoGen.SourceGenerator + href: Create-type-safe-function-call.md + - name: Use function call in an agent + href: Use-function-call.md + - name: middleware + items: + - name: middleware overview + href: Middleware-overview.md + - name: built-in middleware and use case + items: + - name: print message + href: Print-message-middleware.md + # - name: function call + # href: Function-call-middleware.md + - name: group chat + items: + - name: group chat overview + href: Group-chat-overview.md + - name: round robin group chat + href: Roundrobin-chat.md + - name: dynamic group chat + href: Group-chat.md + - name: use graph to control dynamic group chat + href: Use-graph-in-group-chat.md + +- name: AutoGen.DotnetInteractive + items: + - name: Execute code snippet + href: Run-dotnet-code.md + +- name: AutoGen.OpenAI + items: + - name: Overview + href: AutoGen-OpenAI-Overview.md + - name: Examples + items: + - name: Simple chat and streaming chat + href: OpenAIChatAgent-simple-chat.md + - name: Support more AutoGen built-in messages + href: OpenAIChatAgent-support-more-messages.md + - name: Use function call in OpenAIChatAgent + href: OpenAIChatAgent-use-function-call.md + - name: Use json mode in OpenAIChatAgent + href: OpenAIChatAgent-use-json-mode.md + - name: Connect to third-party OpenAI API endpoints. + href: OpenAIChatAgent-connect-to-third-party-api.md + +- name: AutoGen.SemanticKernel + items: + - name: Overview + href: AutoGen-SemanticKernel-Overview.md + - name: Simple chat and streaming chat + href: SemanticKernelAgent-simple-chat.md + - name: Use SemanticKernelChatMessageContentConnector to support more AutoGen built-in messages + href: SemanticKernelAgent-support-more-messages.md +- name: AutoGen.Mistral + items: + - name: Overview + href: AutoGen-Mistral-Overview.md + - name: Examples + items: + - name: Use function call in MistralChatAgent + href: MistralChatAgent-use-function-call.md + - name: Count token usage in MistralChatAgent + href: MistralChatAgent-count-token-usage.md + +- name: AutoGen.LMStudio + items: + - name: Consume LLM server from LM Studio + href: Consume-LLM-server-from-LM-Studio.md + diff --git a/dotnet/website/docfx.json b/dotnet/website/docfx.json new file mode 100644 index 00000000000..e06f9797c1f --- /dev/null +++ b/dotnet/website/docfx.json @@ -0,0 +1,68 @@ +{ + "metadata": [ + { + "src": [ + { + "files": ["src/**/*.csproj"], + "src": "../" + } + ], + "dest": "api", + "includePrivateMembers": false, + "disableGitFeatures": false, + "disableDefaultFilter": false, + "noRestore": false, + "namespaceLayout": "flattened", + "memberLayout": "samePage", + "allowCompilationErrors": false, + "filter": "filterConfig.yml" + } + ], + "build": { + "content": [ + { + "files": [ + "api/**.yml", + "api/index.md" + ] + }, + { + "files": [ + "articles/**.md", + "articles/**/toc.yml", + "toc.yml", + "*.md" + ] + } + ], + "resource": [ + { + "files": [ + "images/**" + ] + } + ], + "output": "_site", + "globalMetadataFiles": [], + "fileMetadataFiles": [], + "template": [ + "default", + "modern", + "template" + ], + "globalMetadata":{ + "_appTitle": "AutoGen for .NET", + "_appName": "AutoGen for .NET", + "_appLogoPath": "images/ag.ico", + "_appFooter": "AutoGen for .NET", + "_appFaviconPath": "images/ag.ico", + "_gitContribute": { + "repo": "https://github.com/microsoft/autogen.git", + "branch": "dotnet" + } + }, + "postProcessors": [], + "keepFileLink": false, + "disableGitFeatures": false + } +} \ No newline at end of file diff --git a/dotnet/website/filterConfig.yml b/dotnet/website/filterConfig.yml new file mode 100644 index 00000000000..936ecbc6718 --- /dev/null +++ b/dotnet/website/filterConfig.yml @@ -0,0 +1,3 @@ +apiRules: +- exclude: + uidRegex: ^AutoGen.SourceGenerator \ No newline at end of file diff --git a/dotnet/website/images/ag.ico b/dotnet/website/images/ag.ico new file mode 100644 index 00000000000..f1789673b09 Binary files /dev/null and b/dotnet/website/images/ag.ico differ diff --git a/dotnet/website/images/ag.svg b/dotnet/website/images/ag.svg new file mode 100644 index 00000000000..eba3ee95281 --- /dev/null +++ b/dotnet/website/images/ag.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/dotnet/website/images/articles/ConnectTo3PartyOpenAI/output.gif b/dotnet/website/images/articles/ConnectTo3PartyOpenAI/output.gif new file mode 100644 index 00000000000..3c037e919da Binary files /dev/null and b/dotnet/website/images/articles/ConnectTo3PartyOpenAI/output.gif differ diff --git a/dotnet/website/images/articles/CreateUserProxyAgent/image-1.png b/dotnet/website/images/articles/CreateUserProxyAgent/image-1.png new file mode 100644 index 00000000000..fd467c44af7 --- /dev/null +++ b/dotnet/website/images/articles/CreateUserProxyAgent/image-1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:91813a034edc3918a27758296d77150d1c8d650911847bdc6a42cca79307714a +size 9009 diff --git a/dotnet/website/images/articles/DynamicGroupChat/dynamicChat.gif b/dotnet/website/images/articles/DynamicGroupChat/dynamicChat.gif new file mode 100644 index 00000000000..d756f674114 --- /dev/null +++ b/dotnet/website/images/articles/DynamicGroupChat/dynamicChat.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5cba3069e9669a1b8013f0b2fa4d191c1d7b0b7919b1664f1f8ec98a90c7a2b2 +size 411517 diff --git a/dotnet/website/images/articles/PrintMessageMiddleware/printMessage.png b/dotnet/website/images/articles/PrintMessageMiddleware/printMessage.png new file mode 100644 index 00000000000..db31ade0de8 --- /dev/null +++ b/dotnet/website/images/articles/PrintMessageMiddleware/printMessage.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7ec3bc40d4e3c1228d5799e448a34521998e7abb700bc978afc790389805ecb4 +size 86924 diff --git a/dotnet/website/images/articles/PrintMessageMiddleware/streamingoutput.gif b/dotnet/website/images/articles/PrintMessageMiddleware/streamingoutput.gif new file mode 100644 index 00000000000..a2afd4f5847 --- /dev/null +++ b/dotnet/website/images/articles/PrintMessageMiddleware/streamingoutput.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:95feb667fe74177506435ca52fcf183fb187a3a407fac0b3b220bd9e8da721c7 +size 547023 diff --git a/dotnet/website/images/articles/SequentialGroupChat/SearcherSummarizer.gif b/dotnet/website/images/articles/SequentialGroupChat/SearcherSummarizer.gif new file mode 100644 index 00000000000..250bf00b8dc --- /dev/null +++ b/dotnet/website/images/articles/SequentialGroupChat/SearcherSummarizer.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c6d8a5a534efaf49ecc796ad3ca8e62fb7a236b55d894bda7a0c258564195b5d +size 620269 diff --git a/dotnet/website/index.md b/dotnet/website/index.md new file mode 100644 index 00000000000..3bc691523e9 --- /dev/null +++ b/dotnet/website/index.md @@ -0,0 +1,4 @@ +--- +_disableTocFilter: true +--- +[!INCLUDE [](./articles/getting-start.md)] \ No newline at end of file diff --git a/dotnet/website/toc.yml b/dotnet/website/toc.yml new file mode 100644 index 00000000000..3931f5e7947 --- /dev/null +++ b/dotnet/website/toc.yml @@ -0,0 +1,14 @@ +- name: Docs + href: articles/ + +- name: API Reference + href: api/ + +- name: Update Log + href: update.md + +- name: Other Languages + dropdown: true + items: + - name: Python + href: https://microsoft.github.io/autogen/ diff --git a/dotnet/website/update.md b/dotnet/website/update.md new file mode 100644 index 00000000000..5b18a3f504b --- /dev/null +++ b/dotnet/website/update.md @@ -0,0 +1,57 @@ +##### Update on 0.0.13 (2024-05-09) +###### New features +- [Issue 2593](https://github.com/microsoft/autogen/issues/2593) Consume SK plugins in Agent. +- [Issue 1893](https://github.com/microsoft/autogen/issues/1893) Support inline-data in ImageMessage +- [Issue 2481](https://github.com/microsoft/autogen/issues/2481) Introduce `ChatCompletionAgent` to `AutoGen.SemanticKernel` +###### API Breaking Changes +- [Issue 2470](https://github.com/microsoft/autogen/issues/2470) Update the return type of `IStreamingAgent.GenerateStreamingReplyAsync` from `Task>` to `IAsyncEnumerable` +- [Issue 2470](https://github.com/microsoft/autogen/issues/2470) Update the return type of `IStreamingMiddleware.InvokeAsync` from `Task>` to `IAsyncEnumerable` +- Mark `RegisterReply`, `RegisterPreProcess` and `RegisterPostProcess` as obsolete. You can replace them with `RegisterMiddleware` + +###### Bug Fixes +- Fix [Issue 2609](https://github.com/microsoft/autogen/issues/2609) Constructor of conversableAgentConfig does not accept LMStudioConfig as ConfigList + +##### Update on 0.0.12 (2024-04-22) +- Add AutoGen.Mistral package to support Mistral.AI models +##### Update on 0.0.11 (2024-04-10) +- Add link to Discord channel in nuget's readme.md +- Document improvements +- In `AutoGen.OpenAI`, update `Azure.AI.OpenAI` to 1.0.0-beta.15 and add support for json mode and deterministic output in `OpenAIChatAgent` [Issue #2346](https://github.com/microsoft/autogen/issues/2346) +- In `AutoGen.SemanticKernel`, update `SemanticKernel` package to 1.7.1 +- [API Breaking Change] Rename `PrintMessageMiddlewareExtension.RegisterPrintFormatMessageHook' to `PrintMessageMiddlewareExtension.RegisterPrintMessage`. +##### Update on 0.0.10 (2024-03-12) +- Rename `Workflow` to `Graph` +- Rename `AddInitializeMessage` to `SendIntroduction` +- Rename `SequentialGroupChat` to `RoundRobinGroupChat` +##### Update on 0.0.9 (2024-03-02) +- Refactor over @AutoGen.Message and introducing `TextMessage`, `ImageMessage`, `MultiModalMessage` and so on. PR [#1676](https://github.com/microsoft/autogen/pull/1676) +- Add `AutoGen.SemanticKernel` to support seamless integration with Semantic Kernel +- Move the agent contract abstraction to `AutoGen.Core` package. The `AutoGen.Core` package provides the abstraction for message type, agent and group chat and doesn't contain dependencies over `Azure.AI.OpenAI` or `Semantic Kernel`. This is useful when you want to leverage AutoGen's abstraction only and want to avoid introducing any other dependencies. +- Move `GPTAgent`, `OpenAIChatAgent` and all openai-dependencies to `AutoGen.OpenAI` +##### Update on 0.0.8 (2024-02-28) +- Fix [#1804](https://github.com/microsoft/autogen/pull/1804) +- Streaming support for IAgent [#1656](https://github.com/microsoft/autogen/pull/1656) +- Streaming support for middleware via `MiddlewareStreamingAgent` [#1656](https://github.com/microsoft/autogen/pull/1656) +- Graph chat support with conditional transition workflow [#1761](https://github.com/microsoft/autogen/pull/1761) +- AutoGen.SourceGenerator: Generate `FunctionContract` from `FunctionAttribute` [#1736](https://github.com/microsoft/autogen/pull/1736) +##### Update on 0.0.7 (2024-02-11) +- Add `AutoGen.LMStudio` to support comsume openai-like API from LMStudio local server +##### Update on 0.0.6 (2024-01-23) +- Add `MiddlewareAgent` +- Use `MiddlewareAgent` to implement existing agent hooks (RegisterPreProcess, RegisterPostProcess, RegisterReply) +- Remove `AutoReplyAgent`, `PreProcessAgent`, `PostProcessAgent` because they are replaced by `MiddlewareAgent` +##### Update on 0.0.5 +- Simplify `IAgent` interface by removing `ChatLLM` Property +- Add `GenerateReplyOptions` to `IAgent.GenerateReplyAsync` which allows user to specify or override the options when generating reply + +##### Update on 0.0.4 +- Move out dependency of Semantic Kernel +- Add type `IChatLLM` as connector to LLM + +##### Update on 0.0.3 +- In AutoGen.SourceGenerator, rename FunctionAttribution to FunctionAttribute +- In AutoGen, refactor over ConversationAgent, UserProxyAgent, and AssistantAgent + +##### Update on 0.0.2 +- update Azure.OpenAI.AI to 1.0.0-beta.12 +- update Semantic kernel to 1.0.1 \ No newline at end of file diff --git a/notebook/agentchat_RetrieveChat.ipynb b/notebook/agentchat_RetrieveChat.ipynb index 0ff689a8ece..adb13ac47bd 100644 --- a/notebook/agentchat_RetrieveChat.ipynb +++ b/notebook/agentchat_RetrieveChat.ipynb @@ -48,14 +48,14 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "models to use: ['gpt-35-turbo']\n" + "models to use: ['gpt-3.5-turbo-0125']\n" ] } ], @@ -73,7 +73,9 @@ "# a vector database instance\n", "from autogen.retrieve_utils import TEXT_FORMATS\n", "\n", - "config_list = autogen.config_list_from_json(env_or_file=\"OAI_CONFIG_LIST\")\n", + "config_list = [\n", + " {\"model\": \"gpt-3.5-turbo-0125\", \"api_key\": \"\", \"api_type\": \"openai\"},\n", + "]\n", "\n", "assert len(config_list) > 0\n", "print(\"models to use: \", [config_list[i][\"model\"] for i in range(len(config_list))])" @@ -97,7 +99,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 2, "metadata": {}, "outputs": [ { @@ -105,7 +107,7 @@ "output_type": "stream", "text": [ "Accepted file formats for `docs_path`:\n", - "['xml', 'htm', 'msg', 'docx', 'org', 'pptx', 'jsonl', 'txt', 'tsv', 'yml', 'json', 'md', 'pdf', 'xlsx', 'csv', 'html', 'log', 'yaml', 'doc', 'odt', 'rtf', 'ppt', 'epub', 'rst']\n" + "['odt', 'xml', 'pdf', 'docx', 'html', 'md', 'htm', 'csv', 'rst', 'org', 'ppt', 'doc', 'log', 'json', 'epub', 'jsonl', 'pptx', 'yml', 'xlsx', 'tsv', 'txt', 'yaml', 'msg', 'rtf']\n" ] } ], @@ -116,7 +118,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -139,7 +141,7 @@ "# `chunk_token_size` is the chunk token size for the retrieve chat. By default, it is set to `max_tokens * 0.6`, here we set it to 2000.\n", "# `custom_text_types` is a list of file types to be processed. Default is `autogen.retrieve_utils.TEXT_FORMATS`.\n", "# This only applies to files under the directories in `docs_path`. Explicitly included files and urls will be chunked regardless of their types.\n", - "# In this example, we set it to [\"mdx\"] to only process markdown files. Since no mdx files are included in the `websit/docs`,\n", + "# In this example, we set it to [\"non-existent-type\"] to only process markdown files. Since no \"non-existent-type\" files are included in the `websit/docs`,\n", "# no files there will be processed. However, the explicitly included urls will still be processed.\n", "ragproxyagent = RetrieveUserProxyAgent(\n", " name=\"ragproxyagent\",\n", @@ -152,12 +154,12 @@ " \"https://raw.githubusercontent.com/microsoft/FLAML/main/website/docs/Research.md\",\n", " os.path.join(os.path.abspath(\"\"), \"..\", \"website\", \"docs\"),\n", " ],\n", - " \"custom_text_types\": [\"mdx\"],\n", + " \"custom_text_types\": [\"non-existent-type\"],\n", " \"chunk_token_size\": 2000,\n", " \"model\": config_list[0][\"model\"],\n", - " \"client\": chromadb.PersistentClient(path=\"/tmp/chromadb\"),\n", - " \"embedding_model\": \"all-mpnet-base-v2\",\n", - " \"get_or_create\": True, # set to False if you don't want to reuse an existing collection, but you'll need to remove the collection manually\n", + " # \"client\": chromadb.PersistentClient(path=\"/tmp/chromadb\"), # deprecated, use \"vector_db\" instead\n", + " \"vector_db\": \"chroma\", # to use the deprecated `client` parameter, set to None and uncomment the line above\n", + " \"overwrite\": False, # set to True if you want to overwrite an existing collection\n", " },\n", " code_execution_config=False, # set to False if you don't want to execute the code\n", ")" @@ -179,14 +181,14 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "INFO:autogen.retrieve_utils:Found 2 chunks.\n" + "2024-04-07 17:30:56,955 - autogen.agentchat.contrib.retrieve_user_proxy_agent - INFO - \u001b[32mUse the existing collection `autogen-docs`.\u001b[0m\n" ] }, { @@ -200,15 +202,16 @@ "name": "stderr", "output_type": "stream", "text": [ - "WARNING:chromadb.segment.impl.vector.local_persistent_hnsw:Number of requested results 20 is greater than number of elements in index 2, updating n_results = 2\n" + "2024-04-07 17:30:59,609 - autogen.agentchat.contrib.retrieve_user_proxy_agent - INFO - Found 2 chunks.\u001b[0m\n", + "Number of requested results 20 is greater than number of elements in index 2, updating n_results = 2\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "doc_ids: [['doc_0']]\n", - "\u001b[32mAdding doc_id doc_0 to context.\u001b[0m\n", + "VectorDB returns doc_ids: [['bdfbc921']]\n", + "\u001b[32mAdding content of doc bdfbc921 to context.\u001b[0m\n", "\u001b[33mragproxyagent\u001b[0m (to assistant):\n", "\n", "You're a retrieve augmented coding assistant. You answer user's questions based on your own knowledge and the\n", @@ -226,6 +229,7 @@ "Context is: # Integrate - Spark\n", "\n", "FLAML has integrated Spark for distributed training. There are two main aspects of integration with Spark:\n", + "\n", "- Use Spark ML estimators for AutoML.\n", "- Use Spark to run training in parallel spark jobs.\n", "\n", @@ -240,6 +244,7 @@ "This utility function takes data in the form of a `pandas.Dataframe` or `pyspark.sql.Dataframe` and converts it into a pandas-on-spark dataframe. It also takes `pandas.Series` or `pyspark.sql.Dataframe` and converts it into a [pandas-on-spark](https://spark.apache.org/docs/latest/api/python/user_guide/pandas_on_spark/index.html) series. If you pass in a `pyspark.pandas.Dataframe`, it will not make any changes.\n", "\n", "This function also accepts optional arguments `index_col` and `default_index_type`.\n", + "\n", "- `index_col` is the column name to use as the index, default is None.\n", "- `default_index_type` is the default index type, default is \"distributed-sequence\". More info about default index type could be found on Spark official [documentation](https://spark.apache.org/docs/latest/api/python/user_guide/pandas_on_spark/options.html#default-index-type)\n", "\n", @@ -248,10 +253,13 @@ "```python\n", "import pandas as pd\n", "from flaml.automl.spark.utils import to_pandas_on_spark\n", + "\n", "# Creating a dictionary\n", - "data = {\"Square_Feet\": [800, 1200, 1800, 1500, 850],\n", - " \"Age_Years\": [20, 15, 10, 7, 25],\n", - " \"Price\": [100000, 200000, 300000, 240000, 120000]}\n", + "data = {\n", + " \"Square_Feet\": [800, 1200, 1800, 1500, 850],\n", + " \"Age_Years\": [20, 15, 10, 7, 25],\n", + " \"Price\": [100000, 200000, 300000, 240000, 120000],\n", + "}\n", "\n", "# Creating a pandas DataFrame\n", "dataframe = pd.DataFrame(data)\n", @@ -264,8 +272,10 @@ "To use Spark ML models you need to format your data appropriately. Specifically, use [`VectorAssembler`](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.ml.feature.VectorAssembler.html) to merge all feature columns into a single vector column.\n", "\n", "Here is an example of how to use it:\n", + "\n", "```python\n", "from pyspark.ml.feature import VectorAssembler\n", + "\n", "columns = psdf.columns\n", "feature_cols = [col for col in columns if col != label]\n", "featurizer = VectorAssembler(inputCols=feature_cols, outputCol=\"features\")\n", @@ -275,10 +285,13 @@ "Later in conducting the experiment, use your pandas-on-spark data like non-spark data and pass them using `X_train, y_train` or `dataframe, label`.\n", "\n", "### Estimators\n", + "\n", "#### Model List\n", + "\n", "- `lgbm_spark`: The class for fine-tuning Spark version LightGBM models, using [SynapseML](https://microsoft.github.io/SynapseML/docs/features/lightgbm/about/) API.\n", "\n", "#### Usage\n", + "\n", "First, prepare your data in the required format as described in the previous section.\n", "\n", "By including the models you intend to try in the `estimators_list` argument to `flaml.automl`, FLAML will start trying configurations for these models. If your input is Spark data, FLAML will also use estimators with the `_spark` postfix by default, even if you haven't specified them.\n", @@ -287,6 +300,7 @@ "\n", "```python\n", "import flaml\n", + "\n", "# prepare your data in pandas-on-spark format as we previously mentioned\n", "\n", "automl = flaml.AutoML()\n", @@ -304,24 +318,25 @@ ")\n", "```\n", "\n", - "\n", "[Link to notebook](https://github.com/microsoft/FLAML/blob/main/notebook/automl_bankrupt_synapseml.ipynb) | [Open in colab](https://colab.research.google.com/github/microsoft/FLAML/blob/main/notebook/automl_bankrupt_synapseml.ipynb)\n", "\n", "## Parallel Spark Jobs\n", + "\n", "You can activate Spark as the parallel backend during parallel tuning in both [AutoML](/docs/Use-Cases/Task-Oriented-AutoML#parallel-tuning) and [Hyperparameter Tuning](/docs/Use-Cases/Tune-User-Defined-Function#parallel-tuning), by setting the `use_spark` to `true`. FLAML will dispatch your job to the distributed Spark backend using [`joblib-spark`](https://github.com/joblib/joblib-spark).\n", "\n", "Please note that you should not set `use_spark` to `true` when applying AutoML and Tuning for Spark Data. This is because only SparkML models will be used for Spark Data in AutoML and Tuning. As SparkML models run in parallel, there is no need to distribute them with `use_spark` again.\n", "\n", "All the Spark-related arguments are stated below. These arguments are available in both Hyperparameter Tuning and AutoML:\n", "\n", - "\n", "- `use_spark`: boolean, default=False | Whether to use spark to run the training in parallel spark jobs. This can be used to accelerate training on large models and large datasets, but will incur more overhead in time and thus slow down training in some cases. GPU training is not supported yet when use_spark is True. For Spark clusters, by default, we will launch one trial per executor. However, sometimes we want to launch more trials than the number of executors (e.g., local mode). In this case, we can set the environment variable `FLAML_MAX_CONCURRENT` to override the detected `num_executors`. The final number of concurrent trials will be the minimum of `n_concurrent_trials` and `num_executors`.\n", - "- `n_concurrent_trials`: int, default=1 | The number of concurrent trials. When n_concurrent_trials > 1, FLAML performs parallel tuning.\n", + "- `n_concurrent_trials`: int, default=1 | The number of concurrent trials. When n_concurrent_trials > 1, FLAML performes parallel tuning.\n", "- `force_cancel`: boolean, default=False | Whether to forcely cancel Spark jobs if the search time exceeded the time budget. Spark jobs include parallel tuning jobs and Spark-based model training jobs.\n", "\n", "An example code snippet for using parallel Spark jobs:\n", + "\n", "```python\n", "import flaml\n", + "\n", "automl_experiment = flaml.AutoML()\n", "automl_settings = {\n", " \"time_budget\": 30,\n", @@ -329,7 +344,7 @@ " \"task\": \"regression\",\n", " \"n_concurrent_trials\": 2,\n", " \"use_spark\": True,\n", - " \"force_cancel\": True, # Activating the force_cancel option can immediately halt Spark jobs once they exceed the allocated time_budget.\n", + " \"force_cancel\": True, # Activating the force_cancel option can immediately halt Spark jobs once they exceed the allocated time_budget.\n", "}\n", "\n", "automl.fit(\n", @@ -339,51 +354,72 @@ ")\n", "```\n", "\n", - "\n", "[Link to notebook](https://github.com/microsoft/FLAML/blob/main/notebook/integrate_spark.ipynb) | [Open in colab](https://colab.research.google.com/github/microsoft/FLAML/blob/main/notebook/integrate_spark.ipynb)\n", "\n", "\n", "\n", - "\n", "--------------------------------------------------------------------------------\n", "\u001b[33massistant\u001b[0m (to ragproxyagent):\n", "\n", - "You can use FLAML's `lgbm_spark` estimator for classification tasks and activate Spark as the parallel backend during training by setting `use_spark` to `True`. Here is an example code snippet:\n", + "To perform a classification task using FLAML and use Spark to do parallel training for 30 seconds and force cancel jobs if the time limit is reached, you can follow these steps:\n", + "\n", + "1. First, convert your data into Spark dataframe format using `to_pandas_on_spark` function from `flaml.automl.spark.utils` module.\n", + "2. Then, format your data for use SparkML models by using `VectorAssembler`.\n", + "3. Define your AutoML settings, including the `metric`, `time_budget`, and `task`.\n", + "4. Use `AutoML` from `flaml` to run AutoML with SparkML models by setting `use_spark` to `true`, and `estimator_list` to a list of spark-based estimators, like `[\"lgbm_spark\"]`.\n", + "5. Set `n_concurrent_trials` to the desired number of parallel jobs and `force_cancel` to `True` to cancel the jobs if the time limit is reached.\n", + "\n", + "Here's an example code snippet for performing classification using FLAML and Spark:\n", "\n", "```python\n", - "import flaml\n", + "import pandas as pd\n", "from flaml.automl.spark.utils import to_pandas_on_spark\n", "from pyspark.ml.feature import VectorAssembler\n", + "import flaml\n", "\n", - "# Assuming you have a Spark DataFrame named 'df' that contains your data\n", - "dataframe = df.toPandas()\n", - "label = \"target\"\n", + "# Creating a dictionary\n", + "data = {\n", + " \"sepal_length\": [5.1, 4.9, 4.7, 4.6, 5.0],\n", + " \"sepal_width\": [3.5, 3.0, 3.2, 3.1, 3.6],\n", + " \"petal_length\": [1.4, 1.4, 1.3, 1.5, 1.4],\n", + " \"petal_width\": [0.2, 0.2, 0.2, 0.2, 0.2],\n", + " \"species\": [\"setosa\", \"setosa\", \"setosa\", \"setosa\", \"setosa\"]\n", + "}\n", + "\n", + "# Creating a pandas DataFrame\n", + "dataframe = pd.DataFrame(data)\n", + "label = \"species\"\n", + "\n", + "# Convert to pandas-on-spark dataframe\n", "psdf = to_pandas_on_spark(dataframe)\n", "\n", + "# Format data for SparkML models\n", "columns = psdf.columns\n", "feature_cols = [col for col in columns if col != label]\n", "featurizer = VectorAssembler(inputCols=feature_cols, outputCol=\"features\")\n", "psdf = featurizer.transform(psdf.to_spark(index_col=\"index\"))[\"index\", \"features\"]\n", "\n", - "# configure and run AutoML\n", - "automl = flaml.AutoML()\n", + "# Define AutoML settings\n", "settings = {\n", " \"time_budget\": 30,\n", " \"metric\": \"accuracy\",\n", - " \"estimator_list\": [\"lgbm_spark\"],\n", " \"task\": \"classification\",\n", - " \"n_jobs\": -1, # Use all available CPUs\n", - " \"use_spark\": True, # Use Spark as the parallel backend\n", - " \"force_cancel\": True # Halt Spark jobs that run for longer than the time budget\n", "}\n", + "\n", + "# Use AutoML with SparkML models and parallel jobs\n", + "automl = flaml.AutoML()\n", "automl.fit(\n", " dataframe=psdf,\n", " label=label,\n", + " estimator_list=[\"lgbm_spark\"],\n", + " use_spark=True,\n", + " n_concurrent_trials=2,\n", + " force_cancel=True,\n", " **settings,\n", ")\n", "```\n", "\n", - "Note that you should not use `use_spark` if you are working with Spark data, because SparkML models already run in parallel.\n", + "Note that the above code assumes the data is small enough to train within 30 seconds. If you have a larger dataset, you may need to increase the `time_budget` and adjust the number of parallel jobs accordingly.\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mragproxyagent\u001b[0m (to assistant):\n", @@ -403,56 +439,49 @@ "name": "stderr", "output_type": "stream", "text": [ - "WARNING:chromadb.segment.impl.vector.local_persistent_hnsw:Number of requested results 60 is greater than number of elements in index 2, updating n_results = 2\n", - "WARNING:chromadb.segment.impl.vector.local_persistent_hnsw:Number of requested results 100 is greater than number of elements in index 2, updating n_results = 2\n" + "Number of requested results 60 is greater than number of elements in index 2, updating n_results = 2\n", + "Number of requested results 100 is greater than number of elements in index 2, updating n_results = 2\n", + "Number of requested results 140 is greater than number of elements in index 2, updating n_results = 2\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "doc_ids: [['doc_0']]\n", - "doc_ids: [['doc_0']]\n" + "VectorDB returns doc_ids: [['bdfbc921']]\n", + "VectorDB returns doc_ids: [['bdfbc921']]\n", + "VectorDB returns doc_ids: [['bdfbc921']]\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "WARNING:chromadb.segment.impl.vector.local_persistent_hnsw:Number of requested results 140 is greater than number of elements in index 2, updating n_results = 2\n" + "Number of requested results 180 is greater than number of elements in index 2, updating n_results = 2\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "doc_ids: [['doc_0']]\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "WARNING:chromadb.segment.impl.vector.local_persistent_hnsw:Number of requested results 180 is greater than number of elements in index 2, updating n_results = 2\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "doc_ids: [['doc_0']]\n", + "VectorDB returns doc_ids: [['bdfbc921']]\n", "\u001b[32mNo more context, will terminate.\u001b[0m\n", "\u001b[33mragproxyagent\u001b[0m (to assistant):\n", "\n", "TERMINATE\n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mragproxyagent\u001b[0m (to assistant):\n", - "\n", - "TERMINATE\n", - "\n", "--------------------------------------------------------------------------------\n" ] + }, + { + "data": { + "text/plain": [ + "ChatResult(chat_id=None, chat_history=[{'content': 'TERMINATE', 'role': 'assistant'}], summary='', cost=({'total_cost': 0.007691, 'gpt-35-turbo': {'cost': 0.007691, 'prompt_tokens': 4242, 'completion_tokens': 664, 'total_tokens': 4906}}, {'total_cost': 0}), human_input=[])" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ @@ -464,7 +493,7 @@ "# The conversation continues until the termination condition is met, in RetrieveChat, the termination condition when no human-in-loop is no code block detected.\n", "# With human-in-loop, the conversation will continue until the user says \"exit\".\n", "code_problem = \"How can I use FLAML to perform a classification task and use spark to do parallel training. Train 30 seconds and force cancel jobs if time limit is reached.\"\n", - "ragproxyagent.initiate_chat(\n", + "chat_result = ragproxyagent.initiate_chat(\n", " assistant, message=ragproxyagent.message_generator, problem=code_problem, search_string=\"spark\"\n", ") # search_string is used as an extra filter for the embeddings search, in this case, we only want to search documents that contain \"spark\"." ] @@ -485,23 +514,23 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "WARNING:chromadb.segment.impl.vector.local_persistent_hnsw:Number of requested results 20 is greater than number of elements in index 2, updating n_results = 2\n" + "Number of requested results 20 is greater than number of elements in index 2, updating n_results = 2\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "doc_ids: [['doc_0', 'doc_1']]\n", - "\u001b[32mAdding doc_id doc_0 to context.\u001b[0m\n", - "\u001b[32mAdding doc_id doc_1 to context.\u001b[0m\n", + "VectorDB returns doc_ids: [['7968cf3c', 'bdfbc921']]\n", + "\u001b[32mAdding content of doc 7968cf3c to context.\u001b[0m\n", + "\u001b[32mAdding content of doc bdfbc921 to context.\u001b[0m\n", "\u001b[33mragproxyagent\u001b[0m (to assistant):\n", "\n", "You're a retrieve augmented coding assistant. You answer user's questions based on your own knowledge and the\n", @@ -516,130 +545,11 @@ "\n", "User's question is: Who is the author of FLAML?\n", "\n", - "Context is: # Integrate - Spark\n", - "\n", - "FLAML has integrated Spark for distributed training. There are two main aspects of integration with Spark:\n", - "- Use Spark ML estimators for AutoML.\n", - "- Use Spark to run training in parallel spark jobs.\n", - "\n", - "## Spark ML Estimators\n", - "\n", - "FLAML integrates estimators based on Spark ML models. These models are trained in parallel using Spark, so we called them Spark estimators. To use these models, you first need to organize your data in the required format.\n", - "\n", - "### Data\n", - "\n", - "For Spark estimators, AutoML only consumes Spark data. FLAML provides a convenient function `to_pandas_on_spark` in the `flaml.automl.spark.utils` module to convert your data into a pandas-on-spark (`pyspark.pandas`) dataframe/series, which Spark estimators require.\n", - "\n", - "This utility function takes data in the form of a `pandas.Dataframe` or `pyspark.sql.Dataframe` and converts it into a pandas-on-spark dataframe. It also takes `pandas.Series` or `pyspark.sql.Dataframe` and converts it into a [pandas-on-spark](https://spark.apache.org/docs/latest/api/python/user_guide/pandas_on_spark/index.html) series. If you pass in a `pyspark.pandas.Dataframe`, it will not make any changes.\n", - "\n", - "This function also accepts optional arguments `index_col` and `default_index_type`.\n", - "- `index_col` is the column name to use as the index, default is None.\n", - "- `default_index_type` is the default index type, default is \"distributed-sequence\". More info about default index type could be found on Spark official [documentation](https://spark.apache.org/docs/latest/api/python/user_guide/pandas_on_spark/options.html#default-index-type)\n", - "\n", - "Here is an example code snippet for Spark Data:\n", - "\n", - "```python\n", - "import pandas as pd\n", - "from flaml.automl.spark.utils import to_pandas_on_spark\n", - "# Creating a dictionary\n", - "data = {\"Square_Feet\": [800, 1200, 1800, 1500, 850],\n", - " \"Age_Years\": [20, 15, 10, 7, 25],\n", - " \"Price\": [100000, 200000, 300000, 240000, 120000]}\n", - "\n", - "# Creating a pandas DataFrame\n", - "dataframe = pd.DataFrame(data)\n", - "label = \"Price\"\n", - "\n", - "# Convert to pandas-on-spark dataframe\n", - "psdf = to_pandas_on_spark(dataframe)\n", - "```\n", - "\n", - "To use Spark ML models you need to format your data appropriately. Specifically, use [`VectorAssembler`](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.ml.feature.VectorAssembler.html) to merge all feature columns into a single vector column.\n", - "\n", - "Here is an example of how to use it:\n", - "```python\n", - "from pyspark.ml.feature import VectorAssembler\n", - "columns = psdf.columns\n", - "feature_cols = [col for col in columns if col != label]\n", - "featurizer = VectorAssembler(inputCols=feature_cols, outputCol=\"features\")\n", - "psdf = featurizer.transform(psdf.to_spark(index_col=\"index\"))[\"index\", \"features\"]\n", - "```\n", - "\n", - "Later in conducting the experiment, use your pandas-on-spark data like non-spark data and pass them using `X_train, y_train` or `dataframe, label`.\n", - "\n", - "### Estimators\n", - "#### Model List\n", - "- `lgbm_spark`: The class for fine-tuning Spark version LightGBM models, using [SynapseML](https://microsoft.github.io/SynapseML/docs/features/lightgbm/about/) API.\n", - "\n", - "#### Usage\n", - "First, prepare your data in the required format as described in the previous section.\n", - "\n", - "By including the models you intend to try in the `estimators_list` argument to `flaml.automl`, FLAML will start trying configurations for these models. If your input is Spark data, FLAML will also use estimators with the `_spark` postfix by default, even if you haven't specified them.\n", - "\n", - "Here is an example code snippet using SparkML models in AutoML:\n", - "\n", - "```python\n", - "import flaml\n", - "# prepare your data in pandas-on-spark format as we previously mentioned\n", - "\n", - "automl = flaml.AutoML()\n", - "settings = {\n", - " \"time_budget\": 30,\n", - " \"metric\": \"r2\",\n", - " \"estimator_list\": [\"lgbm_spark\"], # this setting is optional\n", - " \"task\": \"regression\",\n", - "}\n", - "\n", - "automl.fit(\n", - " dataframe=psdf,\n", - " label=label,\n", - " **settings,\n", - ")\n", - "```\n", - "\n", - "\n", - "[Link to notebook](https://github.com/microsoft/FLAML/blob/main/notebook/automl_bankrupt_synapseml.ipynb) | [Open in colab](https://colab.research.google.com/github/microsoft/FLAML/blob/main/notebook/automl_bankrupt_synapseml.ipynb)\n", - "\n", - "## Parallel Spark Jobs\n", - "You can activate Spark as the parallel backend during parallel tuning in both [AutoML](/docs/Use-Cases/Task-Oriented-AutoML#parallel-tuning) and [Hyperparameter Tuning](/docs/Use-Cases/Tune-User-Defined-Function#parallel-tuning), by setting the `use_spark` to `true`. FLAML will dispatch your job to the distributed Spark backend using [`joblib-spark`](https://github.com/joblib/joblib-spark).\n", - "\n", - "Please note that you should not set `use_spark` to `true` when applying AutoML and Tuning for Spark Data. This is because only SparkML models will be used for Spark Data in AutoML and Tuning. As SparkML models run in parallel, there is no need to distribute them with `use_spark` again.\n", - "\n", - "All the Spark-related arguments are stated below. These arguments are available in both Hyperparameter Tuning and AutoML:\n", - "\n", - "\n", - "- `use_spark`: boolean, default=False | Whether to use spark to run the training in parallel spark jobs. This can be used to accelerate training on large models and large datasets, but will incur more overhead in time and thus slow down training in some cases. GPU training is not supported yet when use_spark is True. For Spark clusters, by default, we will launch one trial per executor. However, sometimes we want to launch more trials than the number of executors (e.g., local mode). In this case, we can set the environment variable `FLAML_MAX_CONCURRENT` to override the detected `num_executors`. The final number of concurrent trials will be the minimum of `n_concurrent_trials` and `num_executors`.\n", - "- `n_concurrent_trials`: int, default=1 | The number of concurrent trials. When n_concurrent_trials > 1, FLAML performs parallel tuning.\n", - "- `force_cancel`: boolean, default=False | Whether to forcely cancel Spark jobs if the search time exceeded the time budget. Spark jobs include parallel tuning jobs and Spark-based model training jobs.\n", - "\n", - "An example code snippet for using parallel Spark jobs:\n", - "```python\n", - "import flaml\n", - "automl_experiment = flaml.AutoML()\n", - "automl_settings = {\n", - " \"time_budget\": 30,\n", - " \"metric\": \"r2\",\n", - " \"task\": \"regression\",\n", - " \"n_concurrent_trials\": 2,\n", - " \"use_spark\": True,\n", - " \"force_cancel\": True, # Activating the force_cancel option can immediately halt Spark jobs once they exceed the allocated time_budget.\n", - "}\n", - "\n", - "automl.fit(\n", - " dataframe=dataframe,\n", - " label=label,\n", - " **automl_settings,\n", - ")\n", - "```\n", - "\n", - "\n", - "[Link to notebook](https://github.com/microsoft/FLAML/blob/main/notebook/integrate_spark.ipynb) | [Open in colab](https://colab.research.google.com/github/microsoft/FLAML/blob/main/notebook/integrate_spark.ipynb)\n", - "\n", - "# Research\n", + "Context is: # Research\n", "\n", "For technical details, please check our research publications.\n", "\n", - "* [FLAML: A Fast and Lightweight AutoML Library](https://www.microsoft.com/en-us/research/publication/flaml-a-fast-and-lightweight-automl-library/). Chi Wang, Qingyun Wu, Markus Weimer, Erkang Zhu. MLSys 2021.\n", + "- [FLAML: A Fast and Lightweight AutoML Library](https://www.microsoft.com/en-us/research/publication/flaml-a-fast-and-lightweight-automl-library/). Chi Wang, Qingyun Wu, Markus Weimer, Erkang Zhu. MLSys 2021.\n", "\n", "```bibtex\n", "@inproceedings{wang2021flaml,\n", @@ -650,7 +560,7 @@ "}\n", "```\n", "\n", - "* [Frugal Optimization for Cost-related Hyperparameters](https://arxiv.org/abs/2005.01571). Qingyun Wu, Chi Wang, Silu Huang. AAAI 2021.\n", + "- [Frugal Optimization for Cost-related Hyperparameters](https://arxiv.org/abs/2005.01571). Qingyun Wu, Chi Wang, Silu Huang. AAAI 2021.\n", "\n", "```bibtex\n", "@inproceedings{wu2021cfo,\n", @@ -661,7 +571,7 @@ "}\n", "```\n", "\n", - "* [Economical Hyperparameter Optimization With Blended Search Strategy](https://www.microsoft.com/en-us/research/publication/economical-hyperparameter-optimization-with-blended-search-strategy/). Chi Wang, Qingyun Wu, Silu Huang, Amin Saied. ICLR 2021.\n", + "- [Economical Hyperparameter Optimization With Blended Search Strategy](https://www.microsoft.com/en-us/research/publication/economical-hyperparameter-optimization-with-blended-search-strategy/). Chi Wang, Qingyun Wu, Silu Huang, Amin Saied. ICLR 2021.\n", "\n", "```bibtex\n", "@inproceedings{wang2021blendsearch,\n", @@ -672,7 +582,7 @@ "}\n", "```\n", "\n", - "* [An Empirical Study on Hyperparameter Optimization for Fine-Tuning Pre-trained Language Models](https://aclanthology.org/2021.acl-long.178.pdf). Susan Xueqing Liu, Chi Wang. ACL 2021.\n", + "- [An Empirical Study on Hyperparameter Optimization for Fine-Tuning Pre-trained Language Models](https://aclanthology.org/2021.acl-long.178.pdf). Susan Xueqing Liu, Chi Wang. ACL 2021.\n", "\n", "```bibtex\n", "@inproceedings{liuwang2021hpolm,\n", @@ -683,7 +593,7 @@ "}\n", "```\n", "\n", - "* [ChaCha for Online AutoML](https://www.microsoft.com/en-us/research/publication/chacha-for-online-automl/). Qingyun Wu, Chi Wang, John Langford, Paul Mineiro and Marco Rossi. ICML 2021.\n", + "- [ChaCha for Online AutoML](https://www.microsoft.com/en-us/research/publication/chacha-for-online-automl/). Qingyun Wu, Chi Wang, John Langford, Paul Mineiro and Marco Rossi. ICML 2021.\n", "\n", "```bibtex\n", "@inproceedings{wu2021chacha,\n", @@ -694,7 +604,7 @@ "}\n", "```\n", "\n", - "* [Fair AutoML](https://arxiv.org/abs/2111.06495). Qingyun Wu, Chi Wang. ArXiv preprint arXiv:2111.06495 (2021).\n", + "- [Fair AutoML](https://arxiv.org/abs/2111.06495). Qingyun Wu, Chi Wang. ArXiv preprint arXiv:2111.06495 (2021).\n", "\n", "```bibtex\n", "@inproceedings{wuwang2021fairautoml,\n", @@ -705,7 +615,7 @@ "}\n", "```\n", "\n", - "* [Mining Robust Default Configurations for Resource-constrained AutoML](https://arxiv.org/abs/2202.09927). Moe Kayali, Chi Wang. ArXiv preprint arXiv:2202.09927 (2022).\n", + "- [Mining Robust Default Configurations for Resource-constrained AutoML](https://arxiv.org/abs/2202.09927). Moe Kayali, Chi Wang. ArXiv preprint arXiv:2202.09927 (2022).\n", "\n", "```bibtex\n", "@inproceedings{kayaliwang2022default,\n", @@ -716,7 +626,7 @@ "}\n", "```\n", "\n", - "* [Targeted Hyperparameter Optimization with Lexicographic Preferences Over Multiple Objectives](https://openreview.net/forum?id=0Ij9_q567Ma). Shaokun Zhang, Feiran Jia, Chi Wang, Qingyun Wu. ICLR 2023 (notable-top-5%).\n", + "- [Targeted Hyperparameter Optimization with Lexicographic Preferences Over Multiple Objectives](https://openreview.net/forum?id=0Ij9_q567Ma). Shaokun Zhang, Feiran Jia, Chi Wang, Qingyun Wu. ICLR 2023 (notable-top-5%).\n", "\n", "```bibtex\n", "@inproceedings{zhang2023targeted,\n", @@ -728,7 +638,7 @@ "}\n", "```\n", "\n", - "* [Cost-Effective Hyperparameter Optimization for Large Language Model Generation Inference](https://arxiv.org/abs/2303.04673). Chi Wang, Susan Xueqing Liu, Ahmed H. Awadallah. ArXiv preprint arXiv:2303.04673 (2023).\n", + "- [Cost-Effective Hyperparameter Optimization for Large Language Model Generation Inference](https://arxiv.org/abs/2303.04673). Chi Wang, Susan Xueqing Liu, Ahmed H. Awadallah. ArXiv preprint arXiv:2303.04673 (2023).\n", "\n", "```bibtex\n", "@inproceedings{wang2023EcoOptiGen,\n", @@ -739,7 +649,7 @@ "}\n", "```\n", "\n", - "* [An Empirical Study on Challenging Math Problem Solving with GPT-4](https://arxiv.org/abs/2306.01337). Yiran Wu, Feiran Jia, Shaokun Zhang, Hangyu Li, Erkang Zhu, Yue Wang, Yin Tat Lee, Richard Peng, Qingyun Wu, Chi Wang. ArXiv preprint arXiv:2306.01337 (2023).\n", + "- [An Empirical Study on Challenging Math Problem Solving with GPT-4](https://arxiv.org/abs/2306.01337). Yiran Wu, Feiran Jia, Shaokun Zhang, Hangyu Li, Erkang Zhu, Yue Wang, Yin Tat Lee, Richard Peng, Qingyun Wu, Chi Wang. ArXiv preprint arXiv:2306.01337 (2023).\n", "\n", "```bibtex\n", "@inproceedings{wu2023empirical,\n", @@ -749,29 +659,10 @@ " booktitle={ArXiv preprint arXiv:2306.01337},\n", "}\n", "```\n", - "\n", - "\n", - "\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[32mAdding doc_id doc_1 to context.\u001b[0m\n", - "\u001b[33mragproxyagent\u001b[0m (to assistant):\n", - "\n", - "You're a retrieve augmented coding assistant. You answer user's questions based on your own knowledge and the\n", - "context provided by the user.\n", - "If you can't answer the question with or without the current context, you should reply exactly `UPDATE CONTEXT`.\n", - "For code generation, you must obey the following rules:\n", - "Rule 1. You MUST NOT install any packages because all the packages needed are already installed.\n", - "Rule 2. You must follow the formats below to write your code:\n", - "```language\n", - "# your code\n", - "```\n", - "\n", - "User's question is: Who is the author of FLAML?\n", - "\n", - "Context is: # Integrate - Spark\n", + "# Integrate - Spark\n", "\n", "FLAML has integrated Spark for distributed training. There are two main aspects of integration with Spark:\n", + "\n", "- Use Spark ML estimators for AutoML.\n", "- Use Spark to run training in parallel spark jobs.\n", "\n", @@ -786,6 +677,7 @@ "This utility function takes data in the form of a `pandas.Dataframe` or `pyspark.sql.Dataframe` and converts it into a pandas-on-spark dataframe. It also takes `pandas.Series` or `pyspark.sql.Dataframe` and converts it into a [pandas-on-spark](https://spark.apache.org/docs/latest/api/python/user_guide/pandas_on_spark/index.html) series. If you pass in a `pyspark.pandas.Dataframe`, it will not make any changes.\n", "\n", "This function also accepts optional arguments `index_col` and `default_index_type`.\n", + "\n", "- `index_col` is the column name to use as the index, default is None.\n", "- `default_index_type` is the default index type, default is \"distributed-sequence\". More info about default index type could be found on Spark official [documentation](https://spark.apache.org/docs/latest/api/python/user_guide/pandas_on_spark/options.html#default-index-type)\n", "\n", @@ -794,10 +686,13 @@ "```python\n", "import pandas as pd\n", "from flaml.automl.spark.utils import to_pandas_on_spark\n", + "\n", "# Creating a dictionary\n", - "data = {\"Square_Feet\": [800, 1200, 1800, 1500, 850],\n", - " \"Age_Years\": [20, 15, 10, 7, 25],\n", - " \"Price\": [100000, 200000, 300000, 240000, 120000]}\n", + "data = {\n", + " \"Square_Feet\": [800, 1200, 1800, 1500, 850],\n", + " \"Age_Years\": [20, 15, 10, 7, 25],\n", + " \"Price\": [100000, 200000, 300000, 240000, 120000],\n", + "}\n", "\n", "# Creating a pandas DataFrame\n", "dataframe = pd.DataFrame(data)\n", @@ -810,8 +705,10 @@ "To use Spark ML models you need to format your data appropriately. Specifically, use [`VectorAssembler`](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.ml.feature.VectorAssembler.html) to merge all feature columns into a single vector column.\n", "\n", "Here is an example of how to use it:\n", + "\n", "```python\n", "from pyspark.ml.feature import VectorAssembler\n", + "\n", "columns = psdf.columns\n", "feature_cols = [col for col in columns if col != label]\n", "featurizer = VectorAssembler(inputCols=feature_cols, outputCol=\"features\")\n", @@ -821,10 +718,13 @@ "Later in conducting the experiment, use your pandas-on-spark data like non-spark data and pass them using `X_train, y_train` or `dataframe, label`.\n", "\n", "### Estimators\n", + "\n", "#### Model List\n", + "\n", "- `lgbm_spark`: The class for fine-tuning Spark version LightGBM models, using [SynapseML](https://microsoft.github.io/SynapseML/docs/features/lightgbm/about/) API.\n", "\n", "#### Usage\n", + "\n", "First, prepare your data in the required format as described in the previous section.\n", "\n", "By including the models you intend to try in the `estimators_list` argument to `flaml.automl`, FLAML will start trying configurations for these models. If your input is Spark data, FLAML will also use estimators with the `_spark` postfix by default, even if you haven't specified them.\n", @@ -833,6 +733,7 @@ "\n", "```python\n", "import flaml\n", + "\n", "# prepare your data in pandas-on-spark format as we previously mentioned\n", "\n", "automl = flaml.AutoML()\n", @@ -850,24 +751,25 @@ ")\n", "```\n", "\n", - "\n", "[Link to notebook](https://github.com/microsoft/FLAML/blob/main/notebook/automl_bankrupt_synapseml.ipynb) | [Open in colab](https://colab.research.google.com/github/microsoft/FLAML/blob/main/notebook/automl_bankrupt_synapseml.ipynb)\n", "\n", "## Parallel Spark Jobs\n", + "\n", "You can activate Spark as the parallel backend during parallel tuning in both [AutoML](/docs/Use-Cases/Task-Oriented-AutoML#parallel-tuning) and [Hyperparameter Tuning](/docs/Use-Cases/Tune-User-Defined-Function#parallel-tuning), by setting the `use_spark` to `true`. FLAML will dispatch your job to the distributed Spark backend using [`joblib-spark`](https://github.com/joblib/joblib-spark).\n", "\n", "Please note that you should not set `use_spark` to `true` when applying AutoML and Tuning for Spark Data. This is because only SparkML models will be used for Spark Data in AutoML and Tuning. As SparkML models run in parallel, there is no need to distribute them with `use_spark` again.\n", "\n", "All the Spark-related arguments are stated below. These arguments are available in both Hyperparameter Tuning and AutoML:\n", "\n", - "\n", "- `use_spark`: boolean, default=False | Whether to use spark to run the training in parallel spark jobs. This can be used to accelerate training on large models and large datasets, but will incur more overhead in time and thus slow down training in some cases. GPU training is not supported yet when use_spark is True. For Spark clusters, by default, we will launch one trial per executor. However, sometimes we want to launch more trials than the number of executors (e.g., local mode). In this case, we can set the environment variable `FLAML_MAX_CONCURRENT` to override the detected `num_executors`. The final number of concurrent trials will be the minimum of `n_concurrent_trials` and `num_executors`.\n", - "- `n_concurrent_trials`: int, default=1 | The number of concurrent trials. When n_concurrent_trials > 1, FLAML performs parallel tuning.\n", + "- `n_concurrent_trials`: int, default=1 | The number of concurrent trials. When n_concurrent_trials > 1, FLAML performes parallel tuning.\n", "- `force_cancel`: boolean, default=False | Whether to forcely cancel Spark jobs if the search time exceeded the time budget. Spark jobs include parallel tuning jobs and Spark-based model training jobs.\n", "\n", "An example code snippet for using parallel Spark jobs:\n", + "\n", "```python\n", "import flaml\n", + "\n", "automl_experiment = flaml.AutoML()\n", "automl_settings = {\n", " \"time_budget\": 30,\n", @@ -875,7 +777,7 @@ " \"task\": \"regression\",\n", " \"n_concurrent_trials\": 2,\n", " \"use_spark\": True,\n", - " \"force_cancel\": True, # Activating the force_cancel option can immediately halt Spark jobs once they exceed the allocated time_budget.\n", + " \"force_cancel\": True, # Activating the force_cancel option can immediately halt Spark jobs once they exceed the allocated time_budget.\n", "}\n", "\n", "automl.fit(\n", @@ -885,134 +787,27 @@ ")\n", "```\n", "\n", - "\n", "[Link to notebook](https://github.com/microsoft/FLAML/blob/main/notebook/integrate_spark.ipynb) | [Open in colab](https://colab.research.google.com/github/microsoft/FLAML/blob/main/notebook/integrate_spark.ipynb)\n", "\n", - "# Research\n", - "\n", - "For technical details, please check our research publications.\n", - "\n", - "* [FLAML: A Fast and Lightweight AutoML Library](https://www.microsoft.com/en-us/research/publication/flaml-a-fast-and-lightweight-automl-library/). Chi Wang, Qingyun Wu, Markus Weimer, Erkang Zhu. MLSys 2021.\n", - "\n", - "```bibtex\n", - "@inproceedings{wang2021flaml,\n", - " title={FLAML: A Fast and Lightweight AutoML Library},\n", - " author={Chi Wang and Qingyun Wu and Markus Weimer and Erkang Zhu},\n", - " year={2021},\n", - " booktitle={MLSys},\n", - "}\n", - "```\n", - "\n", - "* [Frugal Optimization for Cost-related Hyperparameters](https://arxiv.org/abs/2005.01571). Qingyun Wu, Chi Wang, Silu Huang. AAAI 2021.\n", - "\n", - "```bibtex\n", - "@inproceedings{wu2021cfo,\n", - " title={Frugal Optimization for Cost-related Hyperparameters},\n", - " author={Qingyun Wu and Chi Wang and Silu Huang},\n", - " year={2021},\n", - " booktitle={AAAI},\n", - "}\n", - "```\n", - "\n", - "* [Economical Hyperparameter Optimization With Blended Search Strategy](https://www.microsoft.com/en-us/research/publication/economical-hyperparameter-optimization-with-blended-search-strategy/). Chi Wang, Qingyun Wu, Silu Huang, Amin Saied. ICLR 2021.\n", - "\n", - "```bibtex\n", - "@inproceedings{wang2021blendsearch,\n", - " title={Economical Hyperparameter Optimization With Blended Search Strategy},\n", - " author={Chi Wang and Qingyun Wu and Silu Huang and Amin Saied},\n", - " year={2021},\n", - " booktitle={ICLR},\n", - "}\n", - "```\n", - "\n", - "* [An Empirical Study on Hyperparameter Optimization for Fine-Tuning Pre-trained Language Models](https://aclanthology.org/2021.acl-long.178.pdf). Susan Xueqing Liu, Chi Wang. ACL 2021.\n", - "\n", - "```bibtex\n", - "@inproceedings{liuwang2021hpolm,\n", - " title={An Empirical Study on Hyperparameter Optimization for Fine-Tuning Pre-trained Language Models},\n", - " author={Susan Xueqing Liu and Chi Wang},\n", - " year={2021},\n", - " booktitle={ACL},\n", - "}\n", - "```\n", - "\n", - "* [ChaCha for Online AutoML](https://www.microsoft.com/en-us/research/publication/chacha-for-online-automl/). Qingyun Wu, Chi Wang, John Langford, Paul Mineiro and Marco Rossi. ICML 2021.\n", - "\n", - "```bibtex\n", - "@inproceedings{wu2021chacha,\n", - " title={ChaCha for Online AutoML},\n", - " author={Qingyun Wu and Chi Wang and John Langford and Paul Mineiro and Marco Rossi},\n", - " year={2021},\n", - " booktitle={ICML},\n", - "}\n", - "```\n", - "\n", - "* [Fair AutoML](https://arxiv.org/abs/2111.06495). Qingyun Wu, Chi Wang. ArXiv preprint arXiv:2111.06495 (2021).\n", - "\n", - "```bibtex\n", - "@inproceedings{wuwang2021fairautoml,\n", - " title={Fair AutoML},\n", - " author={Qingyun Wu and Chi Wang},\n", - " year={2021},\n", - " booktitle={ArXiv preprint arXiv:2111.06495},\n", - "}\n", - "```\n", - "\n", - "* [Mining Robust Default Configurations for Resource-constrained AutoML](https://arxiv.org/abs/2202.09927). Moe Kayali, Chi Wang. ArXiv preprint arXiv:2202.09927 (2022).\n", - "\n", - "```bibtex\n", - "@inproceedings{kayaliwang2022default,\n", - " title={Mining Robust Default Configurations for Resource-constrained AutoML},\n", - " author={Moe Kayali and Chi Wang},\n", - " year={2022},\n", - " booktitle={ArXiv preprint arXiv:2202.09927},\n", - "}\n", - "```\n", - "\n", - "* [Targeted Hyperparameter Optimization with Lexicographic Preferences Over Multiple Objectives](https://openreview.net/forum?id=0Ij9_q567Ma). Shaokun Zhang, Feiran Jia, Chi Wang, Qingyun Wu. ICLR 2023 (notable-top-5%).\n", - "\n", - "```bibtex\n", - "@inproceedings{zhang2023targeted,\n", - " title={Targeted Hyperparameter Optimization with Lexicographic Preferences Over Multiple Objectives},\n", - " author={Shaokun Zhang and Feiran Jia and Chi Wang and Qingyun Wu},\n", - " booktitle={International Conference on Learning Representations},\n", - " year={2023},\n", - " url={https://openreview.net/forum?id=0Ij9_q567Ma},\n", - "}\n", - "```\n", - "\n", - "* [Cost-Effective Hyperparameter Optimization for Large Language Model Generation Inference](https://arxiv.org/abs/2303.04673). Chi Wang, Susan Xueqing Liu, Ahmed H. Awadallah. ArXiv preprint arXiv:2303.04673 (2023).\n", - "\n", - "```bibtex\n", - "@inproceedings{wang2023EcoOptiGen,\n", - " title={Cost-Effective Hyperparameter Optimization for Large Language Model Generation Inference},\n", - " author={Chi Wang and Susan Xueqing Liu and Ahmed H. Awadallah},\n", - " year={2023},\n", - " booktitle={ArXiv preprint arXiv:2303.04673},\n", - "}\n", - "```\n", - "\n", - "* [An Empirical Study on Challenging Math Problem Solving with GPT-4](https://arxiv.org/abs/2306.01337). Yiran Wu, Feiran Jia, Shaokun Zhang, Hangyu Li, Erkang Zhu, Yue Wang, Yin Tat Lee, Richard Peng, Qingyun Wu, Chi Wang. ArXiv preprint arXiv:2306.01337 (2023).\n", - "\n", - "```bibtex\n", - "@inproceedings{wu2023empirical,\n", - " title={An Empirical Study on Challenging Math Problem Solving with GPT-4},\n", - " author={Yiran Wu and Feiran Jia and Shaokun Zhang and Hangyu Li and Erkang Zhu and Yue Wang and Yin Tat Lee and Richard Peng and Qingyun Wu and Chi Wang},\n", - " year={2023},\n", - " booktitle={ArXiv preprint arXiv:2306.01337},\n", - "}\n", - "```\n", - "\n", - "\n", "\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33massistant\u001b[0m (to ragproxyagent):\n", "\n", - "The authors of FLAML are Chi Wang, Qingyun Wu, Markus Weimer, and Erkang Zhu.\n", + "The author of FLAML is Chi Wang, along with several co-authors for various publications related to FLAML.\n", "\n", "--------------------------------------------------------------------------------\n" ] + }, + { + "data": { + "text/plain": [ + "ChatResult(chat_id=None, chat_history=[{'content': 'You\\'re a retrieve augmented coding assistant. You answer user\\'s questions based on your own knowledge and the\\ncontext provided by the user.\\nIf you can\\'t answer the question with or without the current context, you should reply exactly `UPDATE CONTEXT`.\\nFor code generation, you must obey the following rules:\\nRule 1. You MUST NOT install any packages because all the packages needed are already installed.\\nRule 2. You must follow the formats below to write your code:\\n```language\\n# your code\\n```\\n\\nUser\\'s question is: Who is the author of FLAML?\\n\\nContext is: # Research\\n\\nFor technical details, please check our research publications.\\n\\n- [FLAML: A Fast and Lightweight AutoML Library](https://www.microsoft.com/en-us/research/publication/flaml-a-fast-and-lightweight-automl-library/). Chi Wang, Qingyun Wu, Markus Weimer, Erkang Zhu. MLSys 2021.\\n\\n```bibtex\\n@inproceedings{wang2021flaml,\\n title={FLAML: A Fast and Lightweight AutoML Library},\\n author={Chi Wang and Qingyun Wu and Markus Weimer and Erkang Zhu},\\n year={2021},\\n booktitle={MLSys},\\n}\\n```\\n\\n- [Frugal Optimization for Cost-related Hyperparameters](https://arxiv.org/abs/2005.01571). Qingyun Wu, Chi Wang, Silu Huang. AAAI 2021.\\n\\n```bibtex\\n@inproceedings{wu2021cfo,\\n title={Frugal Optimization for Cost-related Hyperparameters},\\n author={Qingyun Wu and Chi Wang and Silu Huang},\\n year={2021},\\n booktitle={AAAI},\\n}\\n```\\n\\n- [Economical Hyperparameter Optimization With Blended Search Strategy](https://www.microsoft.com/en-us/research/publication/economical-hyperparameter-optimization-with-blended-search-strategy/). Chi Wang, Qingyun Wu, Silu Huang, Amin Saied. ICLR 2021.\\n\\n```bibtex\\n@inproceedings{wang2021blendsearch,\\n title={Economical Hyperparameter Optimization With Blended Search Strategy},\\n author={Chi Wang and Qingyun Wu and Silu Huang and Amin Saied},\\n year={2021},\\n booktitle={ICLR},\\n}\\n```\\n\\n- [An Empirical Study on Hyperparameter Optimization for Fine-Tuning Pre-trained Language Models](https://aclanthology.org/2021.acl-long.178.pdf). Susan Xueqing Liu, Chi Wang. ACL 2021.\\n\\n```bibtex\\n@inproceedings{liuwang2021hpolm,\\n title={An Empirical Study on Hyperparameter Optimization for Fine-Tuning Pre-trained Language Models},\\n author={Susan Xueqing Liu and Chi Wang},\\n year={2021},\\n booktitle={ACL},\\n}\\n```\\n\\n- [ChaCha for Online AutoML](https://www.microsoft.com/en-us/research/publication/chacha-for-online-automl/). Qingyun Wu, Chi Wang, John Langford, Paul Mineiro and Marco Rossi. ICML 2021.\\n\\n```bibtex\\n@inproceedings{wu2021chacha,\\n title={ChaCha for Online AutoML},\\n author={Qingyun Wu and Chi Wang and John Langford and Paul Mineiro and Marco Rossi},\\n year={2021},\\n booktitle={ICML},\\n}\\n```\\n\\n- [Fair AutoML](https://arxiv.org/abs/2111.06495). Qingyun Wu, Chi Wang. ArXiv preprint arXiv:2111.06495 (2021).\\n\\n```bibtex\\n@inproceedings{wuwang2021fairautoml,\\n title={Fair AutoML},\\n author={Qingyun Wu and Chi Wang},\\n year={2021},\\n booktitle={ArXiv preprint arXiv:2111.06495},\\n}\\n```\\n\\n- [Mining Robust Default Configurations for Resource-constrained AutoML](https://arxiv.org/abs/2202.09927). Moe Kayali, Chi Wang. ArXiv preprint arXiv:2202.09927 (2022).\\n\\n```bibtex\\n@inproceedings{kayaliwang2022default,\\n title={Mining Robust Default Configurations for Resource-constrained AutoML},\\n author={Moe Kayali and Chi Wang},\\n year={2022},\\n booktitle={ArXiv preprint arXiv:2202.09927},\\n}\\n```\\n\\n- [Targeted Hyperparameter Optimization with Lexicographic Preferences Over Multiple Objectives](https://openreview.net/forum?id=0Ij9_q567Ma). Shaokun Zhang, Feiran Jia, Chi Wang, Qingyun Wu. ICLR 2023 (notable-top-5%).\\n\\n```bibtex\\n@inproceedings{zhang2023targeted,\\n title={Targeted Hyperparameter Optimization with Lexicographic Preferences Over Multiple Objectives},\\n author={Shaokun Zhang and Feiran Jia and Chi Wang and Qingyun Wu},\\n booktitle={International Conference on Learning Representations},\\n year={2023},\\n url={https://openreview.net/forum?id=0Ij9_q567Ma},\\n}\\n```\\n\\n- [Cost-Effective Hyperparameter Optimization for Large Language Model Generation Inference](https://arxiv.org/abs/2303.04673). Chi Wang, Susan Xueqing Liu, Ahmed H. Awadallah. ArXiv preprint arXiv:2303.04673 (2023).\\n\\n```bibtex\\n@inproceedings{wang2023EcoOptiGen,\\n title={Cost-Effective Hyperparameter Optimization for Large Language Model Generation Inference},\\n author={Chi Wang and Susan Xueqing Liu and Ahmed H. Awadallah},\\n year={2023},\\n booktitle={ArXiv preprint arXiv:2303.04673},\\n}\\n```\\n\\n- [An Empirical Study on Challenging Math Problem Solving with GPT-4](https://arxiv.org/abs/2306.01337). Yiran Wu, Feiran Jia, Shaokun Zhang, Hangyu Li, Erkang Zhu, Yue Wang, Yin Tat Lee, Richard Peng, Qingyun Wu, Chi Wang. ArXiv preprint arXiv:2306.01337 (2023).\\n\\n```bibtex\\n@inproceedings{wu2023empirical,\\n title={An Empirical Study on Challenging Math Problem Solving with GPT-4},\\n author={Yiran Wu and Feiran Jia and Shaokun Zhang and Hangyu Li and Erkang Zhu and Yue Wang and Yin Tat Lee and Richard Peng and Qingyun Wu and Chi Wang},\\n year={2023},\\n booktitle={ArXiv preprint arXiv:2306.01337},\\n}\\n```\\n# Integrate - Spark\\n\\nFLAML has integrated Spark for distributed training. There are two main aspects of integration with Spark:\\n\\n- Use Spark ML estimators for AutoML.\\n- Use Spark to run training in parallel spark jobs.\\n\\n## Spark ML Estimators\\n\\nFLAML integrates estimators based on Spark ML models. These models are trained in parallel using Spark, so we called them Spark estimators. To use these models, you first need to organize your data in the required format.\\n\\n### Data\\n\\nFor Spark estimators, AutoML only consumes Spark data. FLAML provides a convenient function `to_pandas_on_spark` in the `flaml.automl.spark.utils` module to convert your data into a pandas-on-spark (`pyspark.pandas`) dataframe/series, which Spark estimators require.\\n\\nThis utility function takes data in the form of a `pandas.Dataframe` or `pyspark.sql.Dataframe` and converts it into a pandas-on-spark dataframe. It also takes `pandas.Series` or `pyspark.sql.Dataframe` and converts it into a [pandas-on-spark](https://spark.apache.org/docs/latest/api/python/user_guide/pandas_on_spark/index.html) series. If you pass in a `pyspark.pandas.Dataframe`, it will not make any changes.\\n\\nThis function also accepts optional arguments `index_col` and `default_index_type`.\\n\\n- `index_col` is the column name to use as the index, default is None.\\n- `default_index_type` is the default index type, default is \"distributed-sequence\". More info about default index type could be found on Spark official [documentation](https://spark.apache.org/docs/latest/api/python/user_guide/pandas_on_spark/options.html#default-index-type)\\n\\nHere is an example code snippet for Spark Data:\\n\\n```python\\nimport pandas as pd\\nfrom flaml.automl.spark.utils import to_pandas_on_spark\\n\\n# Creating a dictionary\\ndata = {\\n \"Square_Feet\": [800, 1200, 1800, 1500, 850],\\n \"Age_Years\": [20, 15, 10, 7, 25],\\n \"Price\": [100000, 200000, 300000, 240000, 120000],\\n}\\n\\n# Creating a pandas DataFrame\\ndataframe = pd.DataFrame(data)\\nlabel = \"Price\"\\n\\n# Convert to pandas-on-spark dataframe\\npsdf = to_pandas_on_spark(dataframe)\\n```\\n\\nTo use Spark ML models you need to format your data appropriately. Specifically, use [`VectorAssembler`](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.ml.feature.VectorAssembler.html) to merge all feature columns into a single vector column.\\n\\nHere is an example of how to use it:\\n\\n```python\\nfrom pyspark.ml.feature import VectorAssembler\\n\\ncolumns = psdf.columns\\nfeature_cols = [col for col in columns if col != label]\\nfeaturizer = VectorAssembler(inputCols=feature_cols, outputCol=\"features\")\\npsdf = featurizer.transform(psdf.to_spark(index_col=\"index\"))[\"index\", \"features\"]\\n```\\n\\nLater in conducting the experiment, use your pandas-on-spark data like non-spark data and pass them using `X_train, y_train` or `dataframe, label`.\\n\\n### Estimators\\n\\n#### Model List\\n\\n- `lgbm_spark`: The class for fine-tuning Spark version LightGBM models, using [SynapseML](https://microsoft.github.io/SynapseML/docs/features/lightgbm/about/) API.\\n\\n#### Usage\\n\\nFirst, prepare your data in the required format as described in the previous section.\\n\\nBy including the models you intend to try in the `estimators_list` argument to `flaml.automl`, FLAML will start trying configurations for these models. If your input is Spark data, FLAML will also use estimators with the `_spark` postfix by default, even if you haven\\'t specified them.\\n\\nHere is an example code snippet using SparkML models in AutoML:\\n\\n```python\\nimport flaml\\n\\n# prepare your data in pandas-on-spark format as we previously mentioned\\n\\nautoml = flaml.AutoML()\\nsettings = {\\n \"time_budget\": 30,\\n \"metric\": \"r2\",\\n \"estimator_list\": [\"lgbm_spark\"], # this setting is optional\\n \"task\": \"regression\",\\n}\\n\\nautoml.fit(\\n dataframe=psdf,\\n label=label,\\n **settings,\\n)\\n```\\n\\n[Link to notebook](https://github.com/microsoft/FLAML/blob/main/notebook/automl_bankrupt_synapseml.ipynb) | [Open in colab](https://colab.research.google.com/github/microsoft/FLAML/blob/main/notebook/automl_bankrupt_synapseml.ipynb)\\n\\n## Parallel Spark Jobs\\n\\nYou can activate Spark as the parallel backend during parallel tuning in both [AutoML](/docs/Use-Cases/Task-Oriented-AutoML#parallel-tuning) and [Hyperparameter Tuning](/docs/Use-Cases/Tune-User-Defined-Function#parallel-tuning), by setting the `use_spark` to `true`. FLAML will dispatch your job to the distributed Spark backend using [`joblib-spark`](https://github.com/joblib/joblib-spark).\\n\\nPlease note that you should not set `use_spark` to `true` when applying AutoML and Tuning for Spark Data. This is because only SparkML models will be used for Spark Data in AutoML and Tuning. As SparkML models run in parallel, there is no need to distribute them with `use_spark` again.\\n\\nAll the Spark-related arguments are stated below. These arguments are available in both Hyperparameter Tuning and AutoML:\\n\\n- `use_spark`: boolean, default=False | Whether to use spark to run the training in parallel spark jobs. This can be used to accelerate training on large models and large datasets, but will incur more overhead in time and thus slow down training in some cases. GPU training is not supported yet when use_spark is True. For Spark clusters, by default, we will launch one trial per executor. However, sometimes we want to launch more trials than the number of executors (e.g., local mode). In this case, we can set the environment variable `FLAML_MAX_CONCURRENT` to override the detected `num_executors`. The final number of concurrent trials will be the minimum of `n_concurrent_trials` and `num_executors`.\\n- `n_concurrent_trials`: int, default=1 | The number of concurrent trials. When n_concurrent_trials > 1, FLAML performes parallel tuning.\\n- `force_cancel`: boolean, default=False | Whether to forcely cancel Spark jobs if the search time exceeded the time budget. Spark jobs include parallel tuning jobs and Spark-based model training jobs.\\n\\nAn example code snippet for using parallel Spark jobs:\\n\\n```python\\nimport flaml\\n\\nautoml_experiment = flaml.AutoML()\\nautoml_settings = {\\n \"time_budget\": 30,\\n \"metric\": \"r2\",\\n \"task\": \"regression\",\\n \"n_concurrent_trials\": 2,\\n \"use_spark\": True,\\n \"force_cancel\": True, # Activating the force_cancel option can immediately halt Spark jobs once they exceed the allocated time_budget.\\n}\\n\\nautoml.fit(\\n dataframe=dataframe,\\n label=label,\\n **automl_settings,\\n)\\n```\\n\\n[Link to notebook](https://github.com/microsoft/FLAML/blob/main/notebook/integrate_spark.ipynb) | [Open in colab](https://colab.research.google.com/github/microsoft/FLAML/blob/main/notebook/integrate_spark.ipynb)\\n\\n', 'role': 'assistant'}, {'content': 'The author of FLAML is Chi Wang, along with several co-authors for various publications related to FLAML.', 'role': 'user'}], summary='The author of FLAML is Chi Wang, along with several co-authors for various publications related to FLAML.', cost=({'total_cost': 0.004711, 'gpt-35-turbo': {'cost': 0.004711, 'prompt_tokens': 3110, 'completion_tokens': 23, 'total_tokens': 3133}}, {'total_cost': 0}), human_input=[])" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ @@ -1020,7 +815,7 @@ "assistant.reset()\n", "\n", "qa_problem = \"Who is the author of FLAML?\"\n", - "ragproxyagent.initiate_chat(assistant, message=ragproxyagent.message_generator, problem=qa_problem)" + "chat_result = ragproxyagent.initiate_chat(assistant, message=ragproxyagent.message_generator, problem=qa_problem)" ] }, { @@ -1433,7 +1228,7 @@ "# set `human_input_mode` to be `ALWAYS`, so the agent will ask for human input at every step.\n", "ragproxyagent.human_input_mode = \"ALWAYS\"\n", "code_problem = \"how to build a time series forecasting model for stock price using FLAML?\"\n", - "ragproxyagent.initiate_chat(assistant, message=ragproxyagent.message_generator, problem=code_problem)" + "chat_result = ragproxyagent.initiate_chat(assistant, message=ragproxyagent.message_generator, problem=code_problem)" ] }, { @@ -1991,7 +1786,7 @@ "# set `human_input_mode` to be `ALWAYS`, so the agent will ask for human input at every step.\n", "ragproxyagent.human_input_mode = \"ALWAYS\"\n", "qa_problem = \"Is there a function named `tune_automl` in FLAML?\"\n", - "ragproxyagent.initiate_chat(\n", + "chat_result = ragproxyagent.initiate_chat(\n", " assistant, message=ragproxyagent.message_generator, problem=qa_problem\n", ") # type \"exit\" to exit the conversation" ] @@ -2584,7 +2379,9 @@ " assistant.reset()\n", "\n", " qa_problem = questions[i]\n", - " ragproxyagent.initiate_chat(assistant, message=ragproxyagent.message_generator, problem=qa_problem, n_results=30)" + " chat_result = ragproxyagent.initiate_chat(\n", + " assistant, message=ragproxyagent.message_generator, problem=qa_problem, n_results=30\n", + " )" ] }, { @@ -3011,7 +2808,9 @@ " assistant.reset()\n", "\n", " qa_problem = questions[i]\n", - " ragproxyagent.initiate_chat(assistant, message=ragproxyagent.message_generator, problem=qa_problem, n_results=10)" + " chat_result = ragproxyagent.initiate_chat(\n", + " assistant, message=ragproxyagent.message_generator, problem=qa_problem, n_results=10\n", + " )" ] } ], @@ -3037,7 +2836,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.13" + "version": "3.11.9" }, "skip_test": "Requires interactive usage" }, diff --git a/notebook/agentchat_agentoptimizer.ipynb b/notebook/agentchat_agentoptimizer.ipynb index 4b7de9715be..7177703ab06 100644 --- a/notebook/agentchat_agentoptimizer.ipynb +++ b/notebook/agentchat_agentoptimizer.ipynb @@ -1,452 +1,466 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# AgentOptimizer: An Agentic Way to Train Your LLM Agent\n", - "\n", - "AutoGen offers conversable agents powered by LLM, tool, or human, which can be used to perform tasks collectively via automated chat. This framework allows tool use and human participation through multi-agent conversation.\n", - "Please find documentation about this feature [here](https://microsoft.github.io/autogen/docs/Use-Cases/agent_chat).\n", - "\n", - "In traditional ML pipeline, we train a model by updating its parameter according to the loss on the training set, while in the era of LLM agents, how should we train an agent? Here, we take an initial step towards the agent training. Inspired by the [function calling](https://platform.openai.com/docs/guides/function-calling) capabilities provided by OpenAI, we draw an analogy between model parameters and agent functions/skills, and update agent’s functions/skills based on its historical performance on the training set. As an agentic way of training an agent, our approach help enhance the agents’ abilities without requiring access to the LLMs parameters.\n", - "\n", - "In this notebook, we introduce a new class, β€˜AgentOptimizer’, which is able to improve the function list of one Assistant-UserProxy pair according to the historical conversation histories.\n", - "This feature would support agents in improving their ability to solve problems of the same type as previous tasks.\n", - "Specifically, given a set of training data, AgentOptimizer would iteratively prompt the LLM to optimize the existing function list of the AssistantAgent and UserProxyAgent with code implementation if necessary. It also includes two strategies, roll-back, and early-stop, to streamline the training process.\n", - "In the example scenario, we test the proposed AgentOptimizer in solving problems from the [MATH dataset](https://github.com/hendrycks/math). \n", - "\n", - "![AgentEval](../website/blog/2023-12-23-AgentOptimizer/img/agentoptimizer.png)\n", - "\n", - "More information could be found in the [paper](https://arxiv.org/abs/2402.11359).\n", - "\n", - "Authors:\n", - "- [Shaokun Zhang](https://github.com/skzhang1), Ph.D. student at the The Pennsylvania State University\n", - "- [Jieyu Zhang](https://jieyuz2.github.io), Ph.D. student at the University of Washington" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [], - "source": [ - "from typing import Any, Callable, Dict, List, Optional, Tuple, Union\n", - "from autogen.agentchat.contrib.agent_optimizer import AgentOptimizer\n", - "from autogen.agentchat.contrib.math_user_proxy_agent import MathUserProxyAgent\n", - "from autogen.agentchat import Agent\n", - "from openai import BadRequestError\n", - "from autogen.code_utils import extract_code\n", - "from autogen.math_utils import get_answer\n", - "from autogen import config_list_from_json\n", - "import autogen\n", - "import json\n", - "import copy" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# MathUserProxy with function_call\n", - "\n", - "This agent is a customozied MathUserProxy inherits from its [partent class](https://github.com/microsoft/autogen/blob/main/autogen/agentchat/contrib/math_user_proxy_agent.py.).\n", - "\n", - "It supports using both function_call and python to solve math problems.\n" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [], - "source": [ - "def is_termination_msg_mathchat(message):\n", - " \"\"\"Check if a message is a termination message.\"\"\"\n", - " if isinstance(message, dict):\n", - " message = message.get(\"content\")\n", - " if message is None:\n", - " return False\n", - " cb = extract_code(message)\n", - " contain_code = False\n", - " for c in cb:\n", - " if c[0] == \"python\":\n", - " contain_code = True\n", - " break\n", - " if message.rstrip().find(\"TERMINATE\") >= 0:\n", - " return True\n", - " return not contain_code and get_answer(message) is not None and get_answer(message) != \"\"\n", - "\n", - "\n", - "class MathUserProxyAgent(MathUserProxyAgent):\n", - " MAX_CONSECUTIVE_AUTO_REPLY = 15\n", - " DEFAULT_REPLY = \"Continue. Please keep solving the problem until you need to query. (If you get to the answer, put it in \\\\boxed{}.)\"\n", - " PROMPTS = \"\"\"Let's solve a math problem.\n", - "Query requirements:\n", - "You should always use the 'print' function for the output and use fractions/radical forms instead of decimals.\n", - "You can use packages like sympy to help you.\n", - "You must follow the formats below to write your code:\n", - "```python\n", - "# your code\n", - "```\n", - "If some packages are missing, you could also suggest a code to install the corresponding package.\n", - "\n", - "Please follow this process:\n", - "1. Solve the problem step by step (do not over-divide the steps).\n", - "2. Take out any queries that can be asked through Python code (for example, any calculations or equations that can be calculated) and functions you know in the context of this conversation.\n", - "\n", - "Please\n", - "(1) do not mix suggested Python codes and function calls in one step.\n", - "(2) You MUST remember that you don’t have a function named \"python\" available.\n", - "\n", - "You must follow the formats below to write your Python code:\n", - "```python\n", - "# your code\n", - "```\n", - "\n", - "3. Wait for me to give the results or wait for the executed results of the function call.\n", - "4. Continue if you think the result is correct. If the result is invalid or unexpected, please correct your query or reasoning.\n", - "\n", - "After all the queries are run and you get the answer, put the answer in \\\\boxed{}.\n", - "\n", - "Problem:\n", - "\"\"\"\n", - "\n", - " def __init__(\n", - " self,\n", - " name: Optional[str] = \"MathChatAgent\",\n", - " is_termination_msg: Optional[Callable[[Dict], bool]] = is_termination_msg_mathchat,\n", - " human_input_mode: Optional[str] = \"NEVER\",\n", - " default_auto_reply: Optional[Union[str, Dict, None]] = DEFAULT_REPLY,\n", - " max_invalid_q_per_step=3,\n", - " **kwargs,\n", - " ):\n", - " super().__init__(\n", - " name=name,\n", - " is_termination_msg=is_termination_msg,\n", - " human_input_mode=human_input_mode,\n", - " default_auto_reply=default_auto_reply,\n", - " max_invalid_q_per_step=max_invalid_q_per_step,\n", - " **kwargs,\n", - " )\n", - " del self._reply_func_list[2]\n", - " self.register_reply([Agent, None], MathUserProxyAgent._generate_math_reply, position=4)\n", - " del self._reply_func_list[3]\n", - " self.register_reply(\n", - " trigger=autogen.ConversableAgent, reply_func=MathUserProxyAgent.generate_function_call_reply, position=3\n", - " )\n", - " self.register_reply(\n", - " trigger=autogen.ConversableAgent, reply_func=MathUserProxyAgent._check_final_result, position=0\n", - " )\n", - "\n", - " self.max_function_call_trial = 3\n", - " self.query = None\n", - " self.answer = None\n", - " self.is_correct = None\n", - "\n", - " def generate_function_call_reply(\n", - " self,\n", - " messages: Optional[List[Dict]] = None,\n", - " sender: Optional[autogen.ConversableAgent] = None,\n", - " config: Optional[Any] = None,\n", - " ) -> Tuple[bool, Union[Dict, None]]:\n", - " \"\"\"Generate a reply using function call.\"\"\"\n", - " if messages is None:\n", - " messages = self._oai_messages[sender]\n", - " message = messages[-1]\n", - " if \"function_call\" in message:\n", - " is_exec_success, func_return = self.execute_function(message[\"function_call\"])\n", - " if is_exec_success:\n", - " self.max_function_call_trial = 3\n", - " return True, func_return\n", - " else:\n", - " if self.max_function_call_trial == 0:\n", - " error_message = func_return[\"content\"]\n", - " self.max_function_call_trial = 3\n", - " return (\n", - " True,\n", - " \"The func is executed failed many times. \"\n", - " + error_message\n", - " + \". Please directly reply me with TERMINATE. We need to terminate the conversation.\",\n", - " )\n", - " else:\n", - " revise_prompt = \"You may make a wrong function call (It may due the arguments you provided doesn't fit the function arguments like missing required positional argument). \\\n", - " If you think this error occurs due to you make a wrong function arguments input and you could make it success, please try to call this function again using the correct arguments. \\\n", - " Otherwise, the error may be caused by the function itself. Please directly reply me with TERMINATE. We need to terminate the conversation. \"\n", - " error_message = func_return[\"content\"]\n", - " return True, \"The func is executed failed.\" + error_message + revise_prompt\n", - " return False, None\n", - "\n", - " def initiate_chat(\n", - " self,\n", - " recipient,\n", - " answer: None,\n", - " silent: Optional[bool] = False,\n", - " **context,\n", - " ):\n", - " self.query = context[\"problem\"]\n", - " if not isinstance(answer, str):\n", - " answer = str(answer)\n", - " if answer.endswith(\".0\"):\n", - " answer = answer[:-2]\n", - " self._answer = answer\n", - " else:\n", - " self._answer = answer\n", - "\n", - " self.is_correct = None\n", - "\n", - " self._prepare_chat(recipient, True)\n", - " error_message = None\n", - " try:\n", - " prompt = self.PROMPTS + context[\"problem\"]\n", - " self.send(prompt, recipient, silent=silent)\n", - " except BadRequestError as e:\n", - " error_message = str(e)\n", - " self.is_correct = 0\n", - " print(\"error information: {}\".format(error_message))\n", - "\n", - " recipient.reset()\n", - " is_correct = copy.deepcopy(self.is_correct)\n", - " self._reset()\n", - " return is_correct\n", - "\n", - " def _check_final_result(\n", - " self,\n", - " messages: Optional[List[Dict]] = None,\n", - " sender: Optional[autogen.Agent] = None,\n", - " config: Optional[Any] = None,\n", - " ):\n", - "\n", - " messages = messages[-1]\n", - " if isinstance(messages, dict):\n", - " messages = messages.get(\"content\")\n", - " if messages is None:\n", - " return False, None\n", - "\n", - " cb = extract_code(messages)\n", - " contain_code = False\n", - " for c in cb:\n", - " if c[0] == \"python\":\n", - " contain_code = True\n", - " break\n", - " if not contain_code and get_answer(messages) is not None and get_answer(messages) != \"\":\n", - " if get_answer(messages) == self._answer:\n", - " self.is_correct = 1\n", - " return True, \"The result is Correct. Please reply me with TERMINATE.\"\n", - " else:\n", - " self.is_correct = 0\n", - " return False, None\n", - " else:\n", - " return False, None\n", - "\n", - " def _reset(self):\n", - " super()._reset()\n", - " self.max_function_call_trial = 3\n", - " self.is_correct = None\n", - " self.query = None\n", - " self.answer = None" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Load dataset\n", - "\n", - "MATAH dataset contains 12,500 challenging competition mathematics problems. Each problem in MATH has a full step-by-step solution which can be used to teach models to generate answer derivations and explanations. \n", - "\n", - "We strctly follow the [train](https://github.com/lifan-yuan/CRAFT/blob/main/tab_and_math/MATH/dataset/train/algebra.jsonl)/[test](https://github.com/lifan-yuan/CRAFT/blob/main/tab_and_math/MATH/dataset/algebra.jsonl) splits of [Craft](https://github.com/lifan-yuan/CRAFT). Please specific your own path to the dataset. Here we sample the first 10 algebra problems as examples. " - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "metadata": {}, - "outputs": [], - "source": [ - "test_data, train_data = [], []\n", - "with open(\"MATH/dataset/algebra.jsonl\", \"r\", encoding=\"utf-8\") as f:\n", - " for line in f:\n", - " test_data.append(json.loads(line))\n", - "with open(\"MATH/dataset/train/algebra.jsonl\", \"r\", encoding=\"utf-8\") as f:\n", - " for line in f:\n", - " train_data.append(json.loads(line))\n", - "test_data, train_data = test_data[0:10], train_data[0:10]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Agents construction\n", - "\n", - "Constructing MathUserProxyAgent and AssistantAgent used in solving these problems. Here, we use gpt-4-1106-preview to construct the AssistantAgent. " - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "metadata": {}, - "outputs": [], - "source": [ - "config_list = config_list_from_json(env_or_file=\"OAI_CONFIG_LIST\")\n", - "\n", - "assistant = autogen.AssistantAgent(\n", - " name=\"assistant\",\n", - " system_message=\"You are a helpful assistant.\",\n", - " llm_config={\n", - " \"timeout\": 600,\n", - " \"seed\": 42,\n", - " \"config_list\": config_list,\n", - " },\n", - ")\n", - "user_proxy = MathUserProxyAgent(\n", - " name=\"mathproxyagent\",\n", - " human_input_mode=\"NEVER\",\n", - " code_execution_config={\"work_dir\": \"_output\", \"use_docker\": False},\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Test without agent optimizations \n", - "\n", - "Below is the code to get the performance without the agents optimization process. \n", - "\n", - "In this case, the AssistantAgent and MathUserProxyAgent don't have any function calls but solely solve problems with Python." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "sum = 0\n", - "for index, query in enumerate(test_data):\n", - " is_correct = user_proxy.initiate_chat(recipient=assistant, answer=query[\"answer\"], problem=query[\"question\"])\n", - " print(is_correct)\n", - " sum += is_correct\n", - "success_rate_without_agent_training = sum / 10" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Agent Training \n", - "\n", - "Then, we use the AgentOptimizer to iteratively optimize the agents by optimizing the function calls according to the historical conversations and performance.\n", - "The AgentOptimizer yields register_for_llm and register_for_executor at each iteration, which are subsequently utilized to update the assistant and user_proxy agents, respectively. \n", - "Here we optimize these two agents for ten epochs. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "EPOCH = 10\n", - "optimizer_model = \"gpt-4-1106-preview\"\n", - "optimizer = AgentOptimizer(\n", - " max_actions_per_step=3, config_file_or_env=\"OAI_CONFIG_LIST\", optimizer_model=optimizer_model\n", - ")\n", - "for i in range(EPOCH):\n", - " for index, query in enumerate(train_data):\n", - " is_correct = user_proxy.initiate_chat(assistant, answer=query[\"answer\"], problem=query[\"question\"])\n", - " history = assistant.chat_messages_for_summary(user_proxy)\n", - " optimizer.record_one_conversation(history, is_satisfied=is_correct)\n", - " register_for_llm, register_for_exector = optimizer.step()\n", - " for item in register_for_llm:\n", - " assistant.update_function_signature(**item)\n", - " if len(register_for_exector.keys()) > 0:\n", - " user_proxy.register_function(function_map=register_for_exector)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Test with agent optimizations \n", - "\n", - "After agent optimization, the agents obtained a list of functions from the AgentOptimizers after 10 optimization iterations as shown below.\n", - "\n", - "We then show the final performances with/without the agent optimization process. We observe the agents after optimization are obviously better.\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "sum = 0\n", - "for index, query in enumerate(test_data):\n", - " is_correct = user_proxy.initiate_chat(recipient=assistant, answer=query[\"answer\"], problem=query[\"question\"])\n", - " sum += is_correct\n", - "success_rate_with_agent_training = sum / 10" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# AgentOptimizer: An Agentic Way to Train Your LLM Agent\n", + "\n", + "AutoGen offers conversable agents powered by LLM, tool, or human, which can be used to perform tasks collectively via automated chat. This framework allows tool use and human participation through multi-agent conversation.\n", + "Please find documentation about this feature [here](https://microsoft.github.io/autogen/docs/Use-Cases/agent_chat).\n", + "\n", + "In traditional ML pipeline, we train a model by updating its parameter according to the loss on the training set, while in the era of LLM agents, how should we train an agent? Here, we take an initial step towards the agent training. Inspired by the [function calling](https://platform.openai.com/docs/guides/function-calling) capabilities provided by OpenAI, we draw an analogy between model parameters and agent functions/skills, and update agent’s functions/skills based on its historical performance on the training set. As an agentic way of training an agent, our approach help enhance the agents’ abilities without requiring access to the LLMs parameters.\n", + "\n", + "In this notebook, we introduce a new class, β€˜AgentOptimizer’, which is able to improve the function list of one Assistant-UserProxy pair according to the historical conversation histories.\n", + "This feature would support agents in improving their ability to solve problems of the same type as previous tasks.\n", + "Specifically, given a set of training data, AgentOptimizer would iteratively prompt the LLM to optimize the existing function list of the AssistantAgent and UserProxyAgent with code implementation if necessary. It also includes two strategies, roll-back, and early-stop, to streamline the training process.\n", + "In the example scenario, we test the proposed AgentOptimizer in solving problems from the [MATH dataset](https://github.com/hendrycks/math). \n", + "\n", + "![AgentOptimizer](../website/blog/2023-12-23-AgentOptimizer/img/agentoptimizer.png)\n", + "\n", + "More information could be found in the [paper](https://arxiv.org/abs/2402.11359).\n", + "\n", + "Authors:\n", + "- [Shaokun Zhang](https://github.com/skzhang1), Ph.D. student at the The Pennsylvania State University\n", + "- [Jieyu Zhang](https://jieyuz2.github.io), Ph.D. student at the University of Washington" + ] + }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "------------------------------------------------Functions learned------------------------------------------------\n", - "evaluate_expression: Evaluate arithmetic or mathematical expressions provided as strings.\n", - "\n", - "calculate_compound_interest_principal: Calculate the principal amount needed to achieve a certain future value with quarterly compound interest.\n", - "\n", - "solve_linear_system: Solve a system of linear equations represented as coefficients and variables.\n", - "\n", - "------------------------------------------------Summary------------------------------------------------\n", - "\n", - "success_rate_without_agent_training: 60.0%\n", - "\n", - "success_rate_with_agent_training: 90.0%\n", - "\n" - ] + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [], + "source": [ + "import copy\n", + "import json\n", + "import os\n", + "from typing import Any, Callable, Dict, List, Optional, Tuple, Union\n", + "\n", + "from openai import BadRequestError\n", + "\n", + "import autogen\n", + "from autogen import config_list_from_json\n", + "from autogen.agentchat import Agent\n", + "from autogen.agentchat.contrib.agent_optimizer import AgentOptimizer\n", + "from autogen.agentchat.contrib.math_user_proxy_agent import MathUserProxyAgent\n", + "from autogen.code_utils import extract_code\n", + "from autogen.math_utils import get_answer" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# MathUserProxy with function_call\n", + "\n", + "This agent is a customized MathUserProxy inherits from its [parent class](https://github.com/microsoft/autogen/blob/main/autogen/agentchat/contrib/math_user_proxy_agent.py).\n", + "\n", + "It supports using both function_call and python to solve math problems.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [], + "source": [ + "def is_termination_msg_mathchat(message):\n", + " \"\"\"Check if a message is a termination message.\"\"\"\n", + " if isinstance(message, dict):\n", + " message = message.get(\"content\")\n", + " if message is None:\n", + " return False\n", + " cb = extract_code(message)\n", + " contain_code = False\n", + " for c in cb:\n", + " if c[0] == \"python\":\n", + " contain_code = True\n", + " break\n", + " if message.rstrip().find(\"TERMINATE\") >= 0:\n", + " return True\n", + " return not contain_code and get_answer(message) is not None and get_answer(message) != \"\"\n", + "\n", + "\n", + "class MathUserProxyAgent(MathUserProxyAgent):\n", + " MAX_CONSECUTIVE_AUTO_REPLY = 15\n", + " DEFAULT_REPLY = \"Continue. Please keep solving the problem until you need to query. (If you get to the answer, put it in \\\\boxed{}.)\"\n", + " PROMPTS = \"\"\"Let's solve a math problem.\n", + "Query requirements:\n", + "You should always use the 'print' function for the output and use fractions/radical forms instead of decimals.\n", + "You can use packages like sympy to help you.\n", + "You must follow the formats below to write your code:\n", + "```python\n", + "# your code\n", + "```\n", + "If some packages are missing, you could also suggest a code to install the corresponding package.\n", + "\n", + "Please follow this process:\n", + "1. Solve the problem step by step (do not over-divide the steps).\n", + "2. Take out any queries that can be asked through Python code (for example, any calculations or equations that can be calculated) and functions you know in the context of this conversation.\n", + "\n", + "Please\n", + "(1) do not mix suggested Python codes and function calls in one step.\n", + "(2) You MUST remember that you don’t have a function named \"python\" available.\n", + "\n", + "You must follow the formats below to write your Python code:\n", + "```python\n", + "# your code\n", + "```\n", + "\n", + "3. Wait for me to give the results or wait for the executed results of the function call.\n", + "4. Continue if you think the result is correct. If the result is invalid or unexpected, please correct your query or reasoning.\n", + "\n", + "After all the queries are run and you get the answer, put the answer in \\\\boxed{}.\n", + "\n", + "Problem:\n", + "\"\"\"\n", + "\n", + " def __init__(\n", + " self,\n", + " name: Optional[str] = \"MathChatAgent\",\n", + " is_termination_msg: Optional[Callable[[Dict], bool]] = is_termination_msg_mathchat,\n", + " human_input_mode: Optional[str] = \"NEVER\",\n", + " default_auto_reply: Optional[Union[str, Dict, None]] = DEFAULT_REPLY,\n", + " max_invalid_q_per_step=3,\n", + " **kwargs,\n", + " ):\n", + " super().__init__(\n", + " name=name,\n", + " is_termination_msg=is_termination_msg,\n", + " human_input_mode=human_input_mode,\n", + " default_auto_reply=default_auto_reply,\n", + " max_invalid_q_per_step=max_invalid_q_per_step,\n", + " **kwargs,\n", + " )\n", + " del self._reply_func_list[2]\n", + " self.register_reply([Agent, None], MathUserProxyAgent._generate_math_reply, position=4)\n", + " del self._reply_func_list[3]\n", + " self.register_reply(\n", + " trigger=autogen.ConversableAgent, reply_func=MathUserProxyAgent.generate_function_call_reply, position=3\n", + " )\n", + " self.register_reply(\n", + " trigger=autogen.ConversableAgent, reply_func=MathUserProxyAgent._check_final_result, position=0\n", + " )\n", + "\n", + " self.max_function_call_trial = 3\n", + " self.query = None\n", + " self.answer = None\n", + " self.is_correct = None\n", + "\n", + " def generate_function_call_reply(\n", + " self,\n", + " messages: Optional[List[Dict]] = None,\n", + " sender: Optional[autogen.ConversableAgent] = None,\n", + " config: Optional[Any] = None,\n", + " ) -> Tuple[bool, Union[Dict, None]]:\n", + " \"\"\"Generate a reply using function call.\"\"\"\n", + " if messages is None:\n", + " messages = self._oai_messages[sender]\n", + " message = messages[-1]\n", + " if \"function_call\" in message:\n", + " is_exec_success, func_return = self.execute_function(message[\"function_call\"])\n", + " if is_exec_success:\n", + " self.max_function_call_trial = 3\n", + " return True, func_return\n", + " else:\n", + " if self.max_function_call_trial == 0:\n", + " error_message = func_return[\"content\"]\n", + " self.max_function_call_trial = 3\n", + " return (\n", + " True,\n", + " \"The func is executed failed many times. \"\n", + " + error_message\n", + " + \". Please directly reply me with TERMINATE. We need to terminate the conversation.\",\n", + " )\n", + " else:\n", + " revise_prompt = \"You may make a wrong function call (It may due the arguments you provided doesn't fit the function arguments like missing required positional argument). \\\n", + " If you think this error occurs due to you make a wrong function arguments input and you could make it success, please try to call this function again using the correct arguments. \\\n", + " Otherwise, the error may be caused by the function itself. Please directly reply me with TERMINATE. We need to terminate the conversation. \"\n", + " error_message = func_return[\"content\"]\n", + " return True, \"The func is executed failed.\" + error_message + revise_prompt\n", + " return False, None\n", + "\n", + " def initiate_chat(\n", + " self,\n", + " recipient,\n", + " answer: None,\n", + " silent: Optional[bool] = False,\n", + " **context,\n", + " ):\n", + " self.query = context[\"problem\"]\n", + " if not isinstance(answer, str):\n", + " answer = str(answer)\n", + " if answer.endswith(\".0\"):\n", + " answer = answer[:-2]\n", + " self._answer = answer\n", + " else:\n", + " self._answer = answer\n", + "\n", + " self.is_correct = None\n", + "\n", + " self._prepare_chat(recipient, True)\n", + " error_message = None\n", + " try:\n", + " prompt = self.PROMPTS + context[\"problem\"]\n", + " self.send(prompt, recipient, silent=silent)\n", + " except BadRequestError as e:\n", + " error_message = str(e)\n", + " self.is_correct = 0\n", + " print(\"error information: {}\".format(error_message))\n", + "\n", + " recipient.reset()\n", + " is_correct = copy.deepcopy(self.is_correct)\n", + " self._reset()\n", + " return is_correct\n", + "\n", + " def _check_final_result(\n", + " self,\n", + " messages: Optional[List[Dict]] = None,\n", + " sender: Optional[autogen.Agent] = None,\n", + " config: Optional[Any] = None,\n", + " ):\n", + "\n", + " messages = messages[-1]\n", + " if isinstance(messages, dict):\n", + " messages = messages.get(\"content\")\n", + " if messages is None:\n", + " return False, None\n", + "\n", + " cb = extract_code(messages)\n", + " contain_code = False\n", + " for c in cb:\n", + " if c[0] == \"python\":\n", + " contain_code = True\n", + " break\n", + " if not contain_code and get_answer(messages) is not None and get_answer(messages) != \"\":\n", + " if get_answer(messages) == self._answer:\n", + " self.is_correct = 1\n", + " return True, \"The result is Correct. Please reply me with TERMINATE.\"\n", + " else:\n", + " self.is_correct = 0\n", + " return False, None\n", + " else:\n", + " return False, None\n", + "\n", + " def _reset(self):\n", + " super()._reset()\n", + " self.max_function_call_trial = 3\n", + " self.is_correct = None\n", + " self.query = None\n", + " self.answer = None" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Load dataset\n", + "\n", + "MATAH dataset contains 12,500 challenging competition mathematics problems. Each problem in MATH has a full step-by-step solution which can be used to teach models to generate answer derivations and explanations. \n", + "\n", + "We strictly follow the [train](https://github.com/lifan-yuan/CRAFT/blob/main/tab_and_math/MATH/dataset/train/algebra.jsonl)/[test](https://github.com/lifan-yuan/CRAFT/blob/main/tab_and_math/MATH/dataset/algebra.jsonl) splits of [Craft](https://github.com/lifan-yuan/CRAFT). Please specific your own path to the dataset. Here we sample the first 10 algebra problems as examples. " + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [], + "source": [ + "test_data, train_data = [], []\n", + "with open(\"MATH/dataset/algebra.jsonl\", \"r\", encoding=\"utf-8\") as f:\n", + " for line in f:\n", + " test_data.append(json.loads(line))\n", + "with open(\"MATH/dataset/train/algebra.jsonl\", \"r\", encoding=\"utf-8\") as f:\n", + " for line in f:\n", + " train_data.append(json.loads(line))\n", + "test_data, train_data = test_data[0:10], train_data[0:10]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Agents construction\n", + "\n", + "Constructing MathUserProxyAgent and AssistantAgent used in solving these problems. Here, we use gpt-4-1106-preview to construct the AssistantAgent. " + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [], + "source": [ + "llm_config = {\n", + " \"config_list\": [\n", + " {\n", + " \"model\": \"gpt-4-1106-preview\",\n", + " \"api_type\": \"azure\",\n", + " \"api_key\": os.environ[\"AZURE_OPENAI_API_KEY\"],\n", + " \"base_url\": \"https://ENDPOINT.openai.azure.com/\",\n", + " \"api_version\": \"2023-07-01-preview\",\n", + " }\n", + " ]\n", + "}\n", + "\n", + "assistant = autogen.AssistantAgent(\n", + " name=\"assistant\",\n", + " system_message=\"You are a helpful assistant.\",\n", + " llm_config=llm_config,\n", + ")\n", + "user_proxy = MathUserProxyAgent(\n", + " name=\"mathproxyagent\",\n", + " human_input_mode=\"NEVER\",\n", + " code_execution_config={\"work_dir\": \"_output\", \"use_docker\": False},\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Test without agent optimizations \n", + "\n", + "Below is the code to get the performance without the agents optimization process. \n", + "\n", + "In this case, the AssistantAgent and MathUserProxyAgent don't have any function calls but solely solve problems with Python." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sum = 0\n", + "for index, query in enumerate(test_data):\n", + " is_correct = user_proxy.initiate_chat(recipient=assistant, answer=query[\"answer\"], problem=query[\"question\"])\n", + " print(is_correct)\n", + " sum += is_correct\n", + "success_rate_without_agent_training = sum / 10" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Agent Training \n", + "\n", + "Then, we use the AgentOptimizer to iteratively optimize the agents by optimizing the function calls according to the historical conversations and performance.\n", + "The AgentOptimizer yields register_for_llm and register_for_executor at each iteration, which are subsequently utilized to update the assistant and user_proxy agents, respectively. \n", + "Here we optimize these two agents for ten epochs. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "EPOCH = 10\n", + "optimizer_model = \"gpt-4-1106-preview\"\n", + "optimizer = AgentOptimizer(max_actions_per_step=3, llm_config=llm_config, optimizer_model=optimizer_model)\n", + "for i in range(EPOCH):\n", + " for index, query in enumerate(train_data):\n", + " is_correct = user_proxy.initiate_chat(assistant, answer=query[\"answer\"], problem=query[\"question\"])\n", + " history = assistant.chat_messages_for_summary(user_proxy)\n", + " optimizer.record_one_conversation(history, is_satisfied=is_correct)\n", + " register_for_llm, register_for_exector = optimizer.step()\n", + " for item in register_for_llm:\n", + " assistant.update_function_signature(**item)\n", + " if len(register_for_exector.keys()) > 0:\n", + " user_proxy.register_function(function_map=register_for_exector)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Test with agent optimizations \n", + "\n", + "After agent optimization, the agents obtained a list of functions from the AgentOptimizers after 10 optimization iterations as shown below.\n", + "\n", + "We then show the final performances with/without the agent optimization process. We observe the agents after optimization are obviously better.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sum = 0\n", + "for index, query in enumerate(test_data):\n", + " is_correct = user_proxy.initiate_chat(recipient=assistant, answer=query[\"answer\"], problem=query[\"question\"])\n", + " sum += is_correct\n", + "success_rate_with_agent_training = sum / 10" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "------------------------------------------------Functions learned------------------------------------------------\n", + "evaluate_expression: Evaluate arithmetic or mathematical expressions provided as strings.\n", + "\n", + "calculate_compound_interest_principal: Calculate the principal amount needed to achieve a certain future value with quarterly compound interest.\n", + "\n", + "solve_linear_system: Solve a system of linear equations represented as coefficients and variables.\n", + "\n", + "------------------------------------------------Summary------------------------------------------------\n", + "\n", + "success_rate_without_agent_training: 60.0%\n", + "\n", + "success_rate_with_agent_training: 90.0%\n", + "\n" + ] + } + ], + "source": [ + "print(\n", + " \"------------------------------------------------Functions learned------------------------------------------------\"\n", + ")\n", + "for func in assistant.llm_config[\"functions\"]:\n", + " print(func[\"name\"] + \": \" + func[\"description\"] + \"\\n\")\n", + "print(\"------------------------------------------------Summary------------------------------------------------\\n\")\n", + "print(\"success_rate_without_agent_training: {average}%\\n\".format(average=success_rate_without_agent_training * 100))\n", + "print(\"success_rate_with_agent_training: {average}%\\n\".format(average=success_rate_with_agent_training * 100))" + ] + } + ], + "metadata": { + "front_matter": { + "description": "AgentOptimizer is able to prompt LLMs to iteratively optimize function/skills of AutoGen agents according to the historical conversation and performance.", + "tags": [ + "optimization", + "tool/function" + ] + }, + "kernelspec": { + "display_name": "py3.9", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.18" } - ], - "source": [ - "print(\n", - " \"------------------------------------------------Functions learned------------------------------------------------\"\n", - ")\n", - "for func in assistant.llm_config[\"functions\"]:\n", - " print(func[\"name\"] + \": \" + func[\"description\"] + \"\\n\")\n", - "print(\"------------------------------------------------Summary------------------------------------------------\\n\")\n", - "print(\"success_rate_without_agent_training: {average}%\\n\".format(average=success_rate_without_agent_training * 100))\n", - "print(\"success_rate_with_agent_training: {average}%\\n\".format(average=success_rate_with_agent_training * 100))" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "py3.9", - "language": "python", - "name": "python3" }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.18" - } - }, - "nbformat": 4, - "nbformat_minor": 2 + "nbformat": 4, + "nbformat_minor": 2 } diff --git a/notebook/agentchat_auto_feedback_from_code_execution.ipynb b/notebook/agentchat_auto_feedback_from_code_execution.ipynb index e639a6475e1..bf784889d61 100644 --- a/notebook/agentchat_auto_feedback_from_code_execution.ipynb +++ b/notebook/agentchat_auto_feedback_from_code_execution.ipynb @@ -10,16 +10,13 @@ "source": [ "# Task Solving with Code Generation, Execution and Debugging\n", "\n", - "AutoGen offers conversable LLM agents, which can be used to solve various tasks with human or automatic feedback, including tasks that require using tools via code.\n", - "Please find documentation about this feature [here](https://microsoft.github.io/autogen/docs/Use-Cases/agent_chat).\n", - "\n", "In this notebook, we demonstrate how to use `AssistantAgent` and `UserProxyAgent` to write code and execute the code. Here `AssistantAgent` is an LLM-based agent that can write Python code (in a Python coding block) for a user to execute for a given task. `UserProxyAgent` is an agent which serves as a proxy for the human user to execute the code written by `AssistantAgent`, or automatically execute the code. Depending on the setting of `human_input_mode` and `max_consecutive_auto_reply`, the `UserProxyAgent` either solicits feedback from the human user or returns auto-feedback based on the result of code execution (success or failure and corresponding outputs) to `AssistantAgent`. `AssistantAgent` will debug the code and suggest new code if the result contains error. The two agents keep communicating to each other until the task is done.\n", "\n", "````{=mdx}\n", ":::info Requirements\n", - "Install `pyautogen`:\n", + "Install the following packages before running the code below:\n", "```bash\n", - "pip install pyautogen\n", + "pip install pyautogen matplotlib yfinance\n", "```\n", "\n", "For more information, please refer to the [installation guide](/docs/installation/).\n", @@ -29,36 +26,21 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ - "from typing import Dict, Union\n", - "\n", - "from IPython import get_ipython\n", - "from IPython.display import display, Image\n", - "import csv\n", + "from IPython.display import Image, display\n", "\n", "import autogen\n", + "from autogen.coding import LocalCommandLineCodeExecutor\n", "\n", "config_list = autogen.config_list_from_json(\n", " \"OAI_CONFIG_LIST\",\n", - " # filter_dict={\n", - " # \"model\": [\"gpt-4\", \"gpt-4-0314\", \"gpt4\", \"gpt-4-32k\", \"gpt-4-32k-0314\", \"gpt-4-32k-v0314\"],\n", - " # },\n", - ")" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "````{=mdx}\n", - ":::tip\n", - "Learn more about configuring LLMs for agents [here](/docs/topics/llm_configuration).\n", - ":::\n", - "````" + " filter_dict={\"tags\": [\"gpt-4\"]}, # comment out to get all\n", + ")\n", + "# When using a single openai endpoint, you can use the following:\n", + "# config_list = [{\"model\": \"gpt-4\", \"api_key\": os.getenv(\"OPENAI_API_KEY\")}]\n" ] }, { @@ -73,7 +55,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "metadata": {}, "outputs": [ { @@ -87,108 +69,175 @@ "--------------------------------------------------------------------------------\n", "\u001b[33massistant\u001b[0m (to user_proxy):\n", "\n", - "To get the current date, we can use Python's `datetime` module. After that, we will need to retrieve the year-to-date (YTD) gain for both META (Meta Platforms, Inc.) and TESLA (Tesla, Inc.). We can do this by fetching the stock prices from the beginning of the year and the current stock prices, then calculating the percentage change.\n", - "\n", - "First, let's write a Python script to get the current date:\n", + "First, let's get the current date using Python. \n", "\n", "```python\n", - "# filename: get_current_date.py\n", + "# Python code\n", + "from datetime import date\n", + "\n", + "# Get today's date\n", + "today = date.today()\n", + "\n", + "# Print today's date\n", + "print(\"Today's date:\", today)\n", + "```\n", + "\n", + "Next, we need to fetch the stock prices for META (Facebook) and TESLA for the current year. We can use the `yfinance` library in Python to fetch this data. If `yfinance` is not installed, it can be installed using pip: `pip install yfinance`.\n", "\n", + "Here is the Python code to fetch the stock prices and calculate the year-to-date gain:\n", + "\n", + "```python\n", + "# Python code\n", + "import yfinance as yf\n", "from datetime import datetime\n", "\n", - "# Get the current date\n", - "current_date = datetime.now()\n", + "# Get the current year\n", + "current_year = datetime.now().year\n", + "\n", + "# Download stock data for the current year\n", + "meta_data = yf.download('FB', start=f'{current_year}-01-01', end=today)\n", + "tesla_data = yf.download('TSLA', start=f'{current_year}-01-01', end=today)\n", "\n", - "# Print the current date in YYYY-MM-DD format\n", - "print(current_date.strftime('%Y-%m-%d'))\n", + "# Calculate the year-to-date gain for each stock\n", + "meta_ytd_gain = ((meta_data['Close'][-1] - meta_data['Close'][0]) / meta_data['Close'][0]) * 100\n", + "tesla_ytd_gain = ((tesla_data['Close'][-1] - tesla_data['Close'][0]) / tesla_data['Close'][0]) * 100\n", + "\n", + "# Print the year-to-date gain for each stock\n", + "print(f'META year-to-date gain: {meta_ytd_gain}%')\n", + "print(f'TESLA year-to-date gain: {tesla_ytd_gain}%')\n", "```\n", "\n", - "Please save the above code in a file named `get_current_date.py` and execute it to get today's date. After that, we will proceed to the next step of fetching the stock data.\n", + "This code fetches the closing prices for META and TESLA for the current year, calculates the year-to-date gain for each stock, and prints the results. The year-to-date gain is calculated as the percentage change in the closing price from the first trading day of the year to the most recent trading day.\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[31m\n", - ">>>>>>>> EXECUTING CODE BLOCK 0 (inferred language is python)...\u001b[0m\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ + ">>>>>>>> EXECUTING 2 CODE BLOCKS (inferred languages are [python, python])...\u001b[0m\n", "\u001b[33muser_proxy\u001b[0m (to assistant):\n", "\n", - "exitcode: 0 (execution succeeded)\n", - "Code output: \n", - "2024-03-03\n", + "exitcode: 1 (execution failed)\n", + "Code output: Today's date: 2024-04-12\n", + "Traceback (most recent call last):\n", + " File \"/Users/ekzhu/autogen/notebook/coding/tmp_code_cb9ef30baa23cf28e127198c0ebeb7e6.py\", line 9, in \n", + " meta_data = yf.download('FB', start=f'{current_year}-01-01', end=today)\n", + " ^^^^^\n", + "NameError: name 'today' is not defined\n", "\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33massistant\u001b[0m (to user_proxy):\n", "\n", - "Great, today's date is March 3, 2024. Now, let's proceed to the next step, which is to fetch the stock prices for META and TESLA.\n", + "I apologize for the oversight. The variable `today` was defined in the first code block but not in the second one. Let's correct this by defining `today` in the second code block as well. Here's the corrected code:\n", "\n", - "We will use Python to retrieve the stock data. For this purpose, we can use the `yfinance` library, which allows us to fetch historical market data from Yahoo Finance. If `yfinance` is not installed on your system, you will need to install it using `pip install yfinance`.\n", + "```python\n", + "# Python code\n", + "import yfinance as yf\n", + "from datetime import datetime, date\n", "\n", - "Here's the Python script to fetch the YTD gain for META and TESLA:\n", + "# Get the current year and today's date\n", + "current_year = datetime.now().year\n", + "today = date.today()\n", "\n", - "```python\n", - "# filename: ytd_gain_comparison.py\n", + "# Download stock data for the current year\n", + "meta_data = yf.download('FB', start=f'{current_year}-01-01', end=today)\n", + "tesla_data = yf.download('TSLA', start=f'{current_year}-01-01', end=today)\n", + "\n", + "# Calculate the year-to-date gain for each stock\n", + "meta_ytd_gain = ((meta_data['Close'][-1] - meta_data['Close'][0]) / meta_data['Close'][0]) * 100\n", + "tesla_ytd_gain = ((tesla_data['Close'][-1] - tesla_data['Close'][0]) / tesla_data['Close'][0]) * 100\n", + "\n", + "# Print the year-to-date gain for each stock\n", + "print(f'META year-to-date gain: {meta_ytd_gain}%')\n", + "print(f'TESLA year-to-date gain: {tesla_ytd_gain}%')\n", + "```\n", + "\n", + "This code should now correctly fetch the stock prices for META and TESLA, calculate the year-to-date gain for each, and print the results.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[31m\n", + ">>>>>>>> EXECUTING CODE BLOCK (inferred language is python)...\u001b[0m\n", + "\u001b[33muser_proxy\u001b[0m (to assistant):\n", + "\n", + "exitcode: 1 (execution failed)\n", + "Code output: \n", + "[*********************100%%**********************] 1 of 1 completed\n", + "\n", + "1 Failed download:\n", + "['FB']: Exception('%ticker%: No timezone found, symbol may be delisted')\n", + "/Users/ekzhu/miniconda3/envs/autogen/lib/python3.11/site-packages/yfinance/utils.py:775: FutureWarning: The 'unit' keyword in TimedeltaIndex construction is deprecated and will be removed in a future version. Use pd.to_timedelta instead.\n", + " df.index += _pd.TimedeltaIndex(dst_error_hours, 'h')\n", + "\n", + "[*********************100%%**********************] 1 of 1 completed\n", + "/Users/ekzhu/autogen/notebook/coding/tmp_code_9ca584c3d8b484c70ee49c1ce5e4b975.py:14: FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`\n", + " meta_ytd_gain = ((meta_data['Close'][-1] - meta_data['Close'][0]) / meta_data['Close'][0]) * 100\n", + "Traceback (most recent call last):\n", + " File \"/Users/ekzhu/autogen/notebook/coding/tmp_code_9ca584c3d8b484c70ee49c1ce5e4b975.py\", line 14, in \n", + " meta_ytd_gain = ((meta_data['Close'][-1] - meta_data['Close'][0]) / meta_data['Close'][0]) * 100\n", + " ~~~~~~~~~~~~~~~~~~^^^^\n", + " File \"/Users/ekzhu/miniconda3/envs/autogen/lib/python3.11/site-packages/pandas/core/series.py\", line 1109, in __getitem__\n", + " return self._values[key]\n", + " ~~~~~~~~~~~~^^^^^\n", + "IndexError: index -1 is out of bounds for axis 0 with size 0\n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33massistant\u001b[0m (to user_proxy):\n", + "\n", + "I apologize for the confusion. It seems like the ticker symbol for Meta Platforms Inc. (formerly Facebook) has changed from 'FB' to 'META'. Let's correct this in the code. Also, it seems like the data for the current day might not be available yet, so let's fetch the data until the previous day. Here's the corrected code:\n", "\n", + "```python\n", + "# Python code\n", "import yfinance as yf\n", - "from datetime import datetime\n", + "from datetime import datetime, timedelta\n", + "\n", + "# Get the current year and yesterday's date\n", + "current_year = datetime.now().year\n", + "yesterday = datetime.now() - timedelta(1)\n", "\n", - "# Define the tickers for Meta Platforms, Inc. and Tesla, Inc.\n", - "tickers = [\"META\", \"TSLA\"]\n", - "\n", - "# Define the start of the year\n", - "start_of_year = datetime(datetime.now().year, 1, 1)\n", - "\n", - "# Get the current date\n", - "current_date = datetime.now()\n", - "\n", - "# Function to calculate YTD gain\n", - "def calculate_ytd_gain(ticker):\n", - " # Fetch historical data for the ticker\n", - " data = yf.download(ticker, start=start_of_year, end=current_date)\n", - " \n", - " # Get the first available closing price of the year (approximation of the price at the start of the year)\n", - " start_price = data['Close'].iloc[0]\n", - " \n", - " # Get the most recent closing price\n", - " end_price = data['Close'].iloc[-1]\n", - " \n", - " # Calculate the YTD gain\n", - " ytd_gain = ((end_price - start_price) / start_price) * 100\n", - " \n", - " return ytd_gain\n", - "\n", - "# Calculate and print the YTD gain for each ticker\n", - "for ticker in tickers:\n", - " ytd_gain = calculate_ytd_gain(ticker)\n", - " print(f\"{ticker} YTD Gain: {ytd_gain:.2f}%\")\n", + "# Download stock data for the current year\n", + "meta_data = yf.download('META', start=f'{current_year}-01-01', end=yesterday)\n", + "tesla_data = yf.download('TSLA', start=f'{current_year}-01-01', end=yesterday)\n", "\n", + "# Calculate the year-to-date gain for each stock\n", + "meta_ytd_gain = ((meta_data['Close'][-1] - meta_data['Close'][0]) / meta_data['Close'][0]) * 100\n", + "tesla_ytd_gain = ((tesla_data['Close'][-1] - tesla_data['Close'][0]) / tesla_data['Close'][0]) * 100\n", + "\n", + "# Print the year-to-date gain for each stock\n", + "print(f'META year-to-date gain: {meta_ytd_gain}%')\n", + "print(f'TESLA year-to-date gain: {tesla_ytd_gain}%')\n", "```\n", "\n", - "Please save the above code in a file named `ytd_gain_comparison.py` and execute it. The script will output the YTD gain for both META and TESLA. If `yfinance` is not installed, you will need to install it first by running `pip install yfinance`.\n", + "This code should now correctly fetch the stock prices for META and TESLA, calculate the year-to-date gain for each, and print the results.\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[31m\n", - ">>>>>>>> EXECUTING CODE BLOCK 0 (inferred language is python)...\u001b[0m\n", + ">>>>>>>> EXECUTING CODE BLOCK (inferred language is python)...\u001b[0m\n", "\u001b[33muser_proxy\u001b[0m (to assistant):\n", "\n", "exitcode: 0 (execution succeeded)\n", - "Code output: \n", - "META YTD Gain: 45.05%\n", - "TSLA YTD Gain: -18.43%\n", + "Code output: /Users/ekzhu/miniconda3/envs/autogen/lib/python3.11/site-packages/yfinance/utils.py:775: FutureWarning: The 'unit' keyword in TimedeltaIndex construction is deprecated and will be removed in a future version. Use pd.to_timedelta instead.\n", + " df.index += _pd.TimedeltaIndex(dst_error_hours, 'h')\n", + "\n", + "[*********************100%%**********************] 1 of 1 completed\n", + "/Users/ekzhu/miniconda3/envs/autogen/lib/python3.11/site-packages/yfinance/utils.py:775: FutureWarning: The 'unit' keyword in TimedeltaIndex construction is deprecated and will be removed in a future version. Use pd.to_timedelta instead.\n", + " df.index += _pd.TimedeltaIndex(dst_error_hours, 'h')\n", + "\n", + "[*********************100%%**********************] 1 of 1 completed\n", + "/Users/ekzhu/autogen/notebook/coding/tmp_code_0b52d7fcfe06d76c8b1e0ce9333cb5cf.py:14: FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`\n", + " meta_ytd_gain = ((meta_data['Close'][-1] - meta_data['Close'][0]) / meta_data['Close'][0]) * 100\n", + "/Users/ekzhu/autogen/notebook/coding/tmp_code_0b52d7fcfe06d76c8b1e0ce9333cb5cf.py:15: FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`\n", + " tesla_ytd_gain = ((tesla_data['Close'][-1] - tesla_data['Close'][0]) / tesla_data['Close'][0]) * 100\n", + "META year-to-date gain: 50.11406747602124%\n", + "TESLA year-to-date gain: -30.85903076529873%\n", "\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33massistant\u001b[0m (to user_proxy):\n", "\n", - "The year-to-date (YTD) gain for META (Meta Platforms, Inc.) is 45.05%, indicating that the stock price has increased by this percentage since the beginning of the year.\n", + "The code has successfully fetched the stock prices for META and TESLA and calculated the year-to-date gain for each. \n", "\n", - "On the other hand, TESLA (Tesla, Inc.) has a YTD loss of -18.43%, which means that the stock price has decreased by this percentage since the start of the year.\n", + "As of yesterday, the year-to-date gain for META (Meta Platforms Inc.) is approximately 50.11%, and for TESLA (Tesla Inc.) it is approximately -30.86%. This means that so far this year, META's stock price has increased by about 50.11% while TESLA's stock price has decreased by about 30.86%.\n", "\n", - "In summary, as of today, March 3, 2024, META has had a significant gain since the beginning of the year, while TESLA has experienced a decline.\n", + "Please note that stock prices can fluctuate and the exact gain may vary depending on the time of checking.\n", "\n", "TERMINATE\n", "\n", @@ -206,6 +255,7 @@ " \"temperature\": 0, # temperature for sampling\n", " }, # configuration for autogen's enhanced inference API which is compatible with OpenAI API\n", ")\n", + "\n", "# create a UserProxyAgent instance named \"user_proxy\"\n", "user_proxy = autogen.UserProxyAgent(\n", " name=\"user_proxy\",\n", @@ -213,8 +263,8 @@ " max_consecutive_auto_reply=10,\n", " is_termination_msg=lambda x: x.get(\"content\", \"\").rstrip().endswith(\"TERMINATE\"),\n", " code_execution_config={\n", - " \"work_dir\": \"coding\",\n", - " \"use_docker\": False, # Please set use_docker=True if docker is available to run the generated code. Using docker is safer than running the generated code directly.\n", + " # the executor to run the generated code\n", + " \"executor\": LocalCommandLineCodeExecutor(work_dir=\"coding\"),\n", " },\n", ")\n", "# the assistant receives a message from the user_proxy, which contains the task description\n", @@ -230,9 +280,10 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The example above involves code execution. In AutoGen, code execution is triggered automatically by the `UserProxyAgent` when it detects an executable code block in a received message and no human user input is provided. This process occurs in a designated working directory, using a Docker container by default. Unless a specific directory is specified, AutoGen defaults to the `autogen/extensions` directory. Users have the option to specify a different working directory by setting the `work_dir` argument when constructing a new instance of the `UserProxyAgent`.\n", - "\n", - "The whole chat is auto-generated." + "The example above involves code execution. In AutoGen, code execution is triggered automatically by the `UserProxyAgent` when it detects an executable code block in a received message and no human user input is provided. \n", + "Users have the option to specify a different working directory by setting the `work_dir` argument when constructing a new instance of the `LocalCommandLineCodeExecutor`.\n", + "For Docker-based or Jupyter kernel-based code execution, please refer to \n", + "[Code Executors Tutorial](/docs/tutorial/code-executors) for more information." ] }, { @@ -250,16 +301,16 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Chat history: [{'content': 'What date is today? Compare the year-to-date gain for META and TESLA.', 'role': 'assistant'}, {'content': \"To get the current date, we can use Python's `datetime` module. After that, we will need to retrieve the year-to-date (YTD) gain for both META (Meta Platforms, Inc.) and TESLA (Tesla, Inc.). We can do this by fetching the stock prices from the beginning of the year and the current stock prices, then calculating the percentage change.\\n\\nFirst, let's write a Python script to get the current date:\\n\\n```python\\n# filename: get_current_date.py\\n\\nfrom datetime import datetime\\n\\n# Get the current date\\ncurrent_date = datetime.now()\\n\\n# Print the current date in YYYY-MM-DD format\\nprint(current_date.strftime('%Y-%m-%d'))\\n```\\n\\nPlease save the above code in a file named `get_current_date.py` and execute it to get today's date. After that, we will proceed to the next step of fetching the stock data.\", 'role': 'user'}, {'content': 'exitcode: 0 (execution succeeded)\\nCode output: \\n2024-03-03\\n', 'role': 'assistant'}, {'content': 'Great, today\\'s date is March 3, 2024. Now, let\\'s proceed to the next step, which is to fetch the stock prices for META and TESLA.\\n\\nWe will use Python to retrieve the stock data. For this purpose, we can use the `yfinance` library, which allows us to fetch historical market data from Yahoo Finance. If `yfinance` is not installed on your system, you will need to install it using `pip install yfinance`.\\n\\nHere\\'s the Python script to fetch the YTD gain for META and TESLA:\\n\\n```python\\n# filename: ytd_gain_comparison.py\\n\\nimport yfinance as yf\\nfrom datetime import datetime\\n\\n# Define the tickers for Meta Platforms, Inc. and Tesla, Inc.\\ntickers = [\"META\", \"TSLA\"]\\n\\n# Define the start of the year\\nstart_of_year = datetime(datetime.now().year, 1, 1)\\n\\n# Get the current date\\ncurrent_date = datetime.now()\\n\\n# Function to calculate YTD gain\\ndef calculate_ytd_gain(ticker):\\n # Fetch historical data for the ticker\\n data = yf.download(ticker, start=start_of_year, end=current_date)\\n \\n # Get the first available closing price of the year (approximation of the price at the start of the year)\\n start_price = data[\\'Close\\'].iloc[0]\\n \\n # Get the most recent closing price\\n end_price = data[\\'Close\\'].iloc[-1]\\n \\n # Calculate the YTD gain\\n ytd_gain = ((end_price - start_price) / start_price) * 100\\n \\n return ytd_gain\\n\\n# Calculate and print the YTD gain for each ticker\\nfor ticker in tickers:\\n ytd_gain = calculate_ytd_gain(ticker)\\n print(f\"{ticker} YTD Gain: {ytd_gain:.2f}%\")\\n\\n```\\n\\nPlease save the above code in a file named `ytd_gain_comparison.py` and execute it. The script will output the YTD gain for both META and TESLA. If `yfinance` is not installed, you will need to install it first by running `pip install yfinance`.', 'role': 'user'}, {'content': 'exitcode: 0 (execution succeeded)\\nCode output: \\nMETA YTD Gain: 45.05%\\nTSLA YTD Gain: -18.43%\\n', 'role': 'assistant'}, {'content': 'The year-to-date (YTD) gain for META (Meta Platforms, Inc.) is 45.05%, indicating that the stock price has increased by this percentage since the beginning of the year.\\n\\nOn the other hand, TESLA (Tesla, Inc.) has a YTD loss of -18.43%, which means that the stock price has decreased by this percentage since the start of the year.\\n\\nIn summary, as of today, March 3, 2024, META has had a significant gain since the beginning of the year, while TESLA has experienced a decline.\\n\\nTERMINATE', 'role': 'user'}]\n", - "Summary: Today's date is March 3, 2024. The year-to-date (YTD) gain for META (Meta Platforms, Inc.) is 45.05%, indicating an increase in stock price since the beginning of the year. In contrast, TESLA (Tesla, Inc.) has a YTD loss of -18.43%, showing a decrease in stock price over the same period.\n", - "Cost info: ({'total_cost': 0.14834999999999998, 'gpt-4': {'cost': 0.14834999999999998, 'prompt_tokens': 3267, 'completion_tokens': 839, 'total_tokens': 4106}}, {'total_cost': 0})\n" + "Chat history: [{'content': 'What date is today? Compare the year-to-date gain for META and TESLA.', 'role': 'assistant'}, {'content': 'First, let\\'s get the current date using Python. \\n\\n```python\\n# Python code\\nfrom datetime import date\\n\\n# Get today\\'s date\\ntoday = date.today()\\n\\n# Print today\\'s date\\nprint(\"Today\\'s date:\", today)\\n```\\n\\nNext, we need to fetch the stock prices for META (Facebook) and TESLA for the current year. We can use the `yfinance` library in Python to fetch this data. If `yfinance` is not installed, it can be installed using pip: `pip install yfinance`.\\n\\nHere is the Python code to fetch the stock prices and calculate the year-to-date gain:\\n\\n```python\\n# Python code\\nimport yfinance as yf\\nfrom datetime import datetime\\n\\n# Get the current year\\ncurrent_year = datetime.now().year\\n\\n# Download stock data for the current year\\nmeta_data = yf.download(\\'FB\\', start=f\\'{current_year}-01-01\\', end=today)\\ntesla_data = yf.download(\\'TSLA\\', start=f\\'{current_year}-01-01\\', end=today)\\n\\n# Calculate the year-to-date gain for each stock\\nmeta_ytd_gain = ((meta_data[\\'Close\\'][-1] - meta_data[\\'Close\\'][0]) / meta_data[\\'Close\\'][0]) * 100\\ntesla_ytd_gain = ((tesla_data[\\'Close\\'][-1] - tesla_data[\\'Close\\'][0]) / tesla_data[\\'Close\\'][0]) * 100\\n\\n# Print the year-to-date gain for each stock\\nprint(f\\'META year-to-date gain: {meta_ytd_gain}%\\')\\nprint(f\\'TESLA year-to-date gain: {tesla_ytd_gain}%\\')\\n```\\n\\nThis code fetches the closing prices for META and TESLA for the current year, calculates the year-to-date gain for each stock, and prints the results. The year-to-date gain is calculated as the percentage change in the closing price from the first trading day of the year to the most recent trading day.', 'role': 'user'}, {'content': 'exitcode: 1 (execution failed)\\nCode output: Today\\'s date: 2024-04-12\\nTraceback (most recent call last):\\n File \"/Users/ekzhu/autogen/notebook/coding/tmp_code_cb9ef30baa23cf28e127198c0ebeb7e6.py\", line 9, in \\n meta_data = yf.download(\\'FB\\', start=f\\'{current_year}-01-01\\', end=today)\\n ^^^^^\\nNameError: name \\'today\\' is not defined\\n', 'role': 'assistant'}, {'content': \"I apologize for the oversight. The variable `today` was defined in the first code block but not in the second one. Let's correct this by defining `today` in the second code block as well. Here's the corrected code:\\n\\n```python\\n# Python code\\nimport yfinance as yf\\nfrom datetime import datetime, date\\n\\n# Get the current year and today's date\\ncurrent_year = datetime.now().year\\ntoday = date.today()\\n\\n# Download stock data for the current year\\nmeta_data = yf.download('FB', start=f'{current_year}-01-01', end=today)\\ntesla_data = yf.download('TSLA', start=f'{current_year}-01-01', end=today)\\n\\n# Calculate the year-to-date gain for each stock\\nmeta_ytd_gain = ((meta_data['Close'][-1] - meta_data['Close'][0]) / meta_data['Close'][0]) * 100\\ntesla_ytd_gain = ((tesla_data['Close'][-1] - tesla_data['Close'][0]) / tesla_data['Close'][0]) * 100\\n\\n# Print the year-to-date gain for each stock\\nprint(f'META year-to-date gain: {meta_ytd_gain}%')\\nprint(f'TESLA year-to-date gain: {tesla_ytd_gain}%')\\n```\\n\\nThis code should now correctly fetch the stock prices for META and TESLA, calculate the year-to-date gain for each, and print the results.\", 'role': 'user'}, {'content': 'exitcode: 1 (execution failed)\\nCode output: \\n[*********************100%%**********************] 1 of 1 completed\\n\\n1 Failed download:\\n[\\'FB\\']: Exception(\\'%ticker%: No timezone found, symbol may be delisted\\')\\n/Users/ekzhu/miniconda3/envs/autogen/lib/python3.11/site-packages/yfinance/utils.py:775: FutureWarning: The \\'unit\\' keyword in TimedeltaIndex construction is deprecated and will be removed in a future version. Use pd.to_timedelta instead.\\n df.index += _pd.TimedeltaIndex(dst_error_hours, \\'h\\')\\n\\n[*********************100%%**********************] 1 of 1 completed\\n/Users/ekzhu/autogen/notebook/coding/tmp_code_9ca584c3d8b484c70ee49c1ce5e4b975.py:14: FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`\\n meta_ytd_gain = ((meta_data[\\'Close\\'][-1] - meta_data[\\'Close\\'][0]) / meta_data[\\'Close\\'][0]) * 100\\nTraceback (most recent call last):\\n File \"/Users/ekzhu/autogen/notebook/coding/tmp_code_9ca584c3d8b484c70ee49c1ce5e4b975.py\", line 14, in \\n meta_ytd_gain = ((meta_data[\\'Close\\'][-1] - meta_data[\\'Close\\'][0]) / meta_data[\\'Close\\'][0]) * 100\\n ~~~~~~~~~~~~~~~~~~^^^^\\n File \"/Users/ekzhu/miniconda3/envs/autogen/lib/python3.11/site-packages/pandas/core/series.py\", line 1109, in __getitem__\\n return self._values[key]\\n ~~~~~~~~~~~~^^^^^\\nIndexError: index -1 is out of bounds for axis 0 with size 0\\n', 'role': 'assistant'}, {'content': \"I apologize for the confusion. It seems like the ticker symbol for Meta Platforms Inc. (formerly Facebook) has changed from 'FB' to 'META'. Let's correct this in the code. Also, it seems like the data for the current day might not be available yet, so let's fetch the data until the previous day. Here's the corrected code:\\n\\n```python\\n# Python code\\nimport yfinance as yf\\nfrom datetime import datetime, timedelta\\n\\n# Get the current year and yesterday's date\\ncurrent_year = datetime.now().year\\nyesterday = datetime.now() - timedelta(1)\\n\\n# Download stock data for the current year\\nmeta_data = yf.download('META', start=f'{current_year}-01-01', end=yesterday)\\ntesla_data = yf.download('TSLA', start=f'{current_year}-01-01', end=yesterday)\\n\\n# Calculate the year-to-date gain for each stock\\nmeta_ytd_gain = ((meta_data['Close'][-1] - meta_data['Close'][0]) / meta_data['Close'][0]) * 100\\ntesla_ytd_gain = ((tesla_data['Close'][-1] - tesla_data['Close'][0]) / tesla_data['Close'][0]) * 100\\n\\n# Print the year-to-date gain for each stock\\nprint(f'META year-to-date gain: {meta_ytd_gain}%')\\nprint(f'TESLA year-to-date gain: {tesla_ytd_gain}%')\\n```\\n\\nThis code should now correctly fetch the stock prices for META and TESLA, calculate the year-to-date gain for each, and print the results.\", 'role': 'user'}, {'content': \"exitcode: 0 (execution succeeded)\\nCode output: /Users/ekzhu/miniconda3/envs/autogen/lib/python3.11/site-packages/yfinance/utils.py:775: FutureWarning: The 'unit' keyword in TimedeltaIndex construction is deprecated and will be removed in a future version. Use pd.to_timedelta instead.\\n df.index += _pd.TimedeltaIndex(dst_error_hours, 'h')\\n\\n[*********************100%%**********************] 1 of 1 completed\\n/Users/ekzhu/miniconda3/envs/autogen/lib/python3.11/site-packages/yfinance/utils.py:775: FutureWarning: The 'unit' keyword in TimedeltaIndex construction is deprecated and will be removed in a future version. Use pd.to_timedelta instead.\\n df.index += _pd.TimedeltaIndex(dst_error_hours, 'h')\\n\\n[*********************100%%**********************] 1 of 1 completed\\n/Users/ekzhu/autogen/notebook/coding/tmp_code_0b52d7fcfe06d76c8b1e0ce9333cb5cf.py:14: FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`\\n meta_ytd_gain = ((meta_data['Close'][-1] - meta_data['Close'][0]) / meta_data['Close'][0]) * 100\\n/Users/ekzhu/autogen/notebook/coding/tmp_code_0b52d7fcfe06d76c8b1e0ce9333cb5cf.py:15: FutureWarning: Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`\\n tesla_ytd_gain = ((tesla_data['Close'][-1] - tesla_data['Close'][0]) / tesla_data['Close'][0]) * 100\\nMETA year-to-date gain: 50.11406747602124%\\nTESLA year-to-date gain: -30.85903076529873%\\n\", 'role': 'assistant'}, {'content': \"The code has successfully fetched the stock prices for META and TESLA and calculated the year-to-date gain for each. \\n\\nAs of yesterday, the year-to-date gain for META (Meta Platforms Inc.) is approximately 50.11%, and for TESLA (Tesla Inc.) it is approximately -30.86%. This means that so far this year, META's stock price has increased by about 50.11% while TESLA's stock price has decreased by about 30.86%.\\n\\nPlease note that stock prices can fluctuate and the exact gain may vary depending on the time of checking.\\n\\nTERMINATE\", 'role': 'user'}]\n", + "Summary: The year-to-date gain for META (Meta Platforms Inc.) is approximately 50.11%, and for TESLA (Tesla Inc.) it is approximately -30.86%. This means that so far this year, META's stock price has increased by about 50.11% while TESLA's stock price has decreased by about 30.86%.\n", + "Cost info: ({'total_cost': 0.32256, 'gpt-4-0613': {'cost': 0.32256, 'prompt_tokens': 8224, 'completion_tokens': 1264, 'total_tokens': 9488}}, {'total_cost': 0.32256, 'gpt-4-0613': {'cost': 0.32256, 'prompt_tokens': 8224, 'completion_tokens': 1264, 'total_tokens': 9488}})\n" ] } ], @@ -280,7 +331,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "metadata": {}, "outputs": [ { @@ -294,77 +345,66 @@ "--------------------------------------------------------------------------------\n", "\u001b[33massistant\u001b[0m (to user_proxy):\n", "\n", - "To plot a chart of the stock price change YTD for META and TESLA, and to save the data to `stock_price_ytd.csv` and the plot to `stock_price_ytd.png`, we will use Python with the `yfinance`, `pandas`, and `matplotlib` libraries. If `matplotlib` is not installed on your system, you will need to install it using `pip install matplotlib`.\n", + "To plot a chart of the stock price change year-to-date (YTD) for META and TESLA, and to save the data and the plot, we can use the `matplotlib` and `pandas` libraries in Python. If these libraries are not installed, they can be installed using pip: `pip install matplotlib pandas`.\n", "\n", - "Here's the Python script to fetch the stock data, save it to a CSV file, plot the chart, and save the plot to a PNG file:\n", + "Here is the Python code to fetch the stock prices, plot the chart, and save the data and the plot:\n", "\n", "```python\n", - "# filename: plot_stock_price_ytd.py\n", - "\n", + "# Python code\n", "import yfinance as yf\n", - "import pandas as pd\n", "import matplotlib.pyplot as plt\n", - "from datetime import datetime\n", - "\n", - "# Define the tickers for Meta Platforms, Inc. and Tesla, Inc.\n", - "tickers = [\"META\", \"TSLA\"]\n", - "\n", - "# Define the start of the year\n", - "start_of_year = datetime(datetime.now().year, 1, 1)\n", - "\n", - "# Get the current date\n", - "current_date = datetime.now()\n", - "\n", - "# Initialize a dictionary to store data\n", - "stock_data = {}\n", - "\n", - "# Fetch historical data for each ticker\n", - "for ticker in tickers:\n", - " stock_data[ticker] = yf.download(ticker, start=start_of_year, end=current_date)\n", - "\n", - "# Combine the closing prices of each stock into a single DataFrame\n", - "combined_data = pd.DataFrame({\n", - " ticker: data['Close']\n", - " for ticker, data in stock_data.items()\n", - "})\n", + "import pandas as pd\n", + "from datetime import datetime, timedelta\n", "\n", - "# Save the combined data to CSV\n", - "combined_data.to_csv('stock_price_ytd.csv')\n", + "# Get the current year and yesterday's date\n", + "current_year = datetime.now().year\n", + "yesterday = datetime.now() - timedelta(1)\n", "\n", - "# Plot the normalized stock price change YTD\n", - "normalized_data = (combined_data / combined_data.iloc[0]) * 100\n", - "normalized_data.plot(figsize=(10, 5))\n", + "# Download stock data for the current year\n", + "meta_data = yf.download('META', start=f'{current_year}-01-01', end=yesterday)\n", + "tesla_data = yf.download('TSLA', start=f'{current_year}-01-01', end=yesterday)\n", "\n", - "# Set plot title and labels\n", - "plt.title('Stock Price Change YTD')\n", + "# Plot the closing prices\n", + "plt.figure(figsize=(14, 7))\n", + "plt.plot(meta_data['Close'], label='META')\n", + "plt.plot(tesla_data['Close'], label='TESLA')\n", + "plt.title('META vs TESLA Stock Price YTD')\n", "plt.xlabel('Date')\n", - "plt.ylabel('Normalized Price (Base 100)')\n", - "\n", - "# Save the plot to a PNG file\n", + "plt.ylabel('Closing Price')\n", + "plt.legend()\n", + "plt.grid(True)\n", "plt.savefig('stock_price_ytd.png')\n", "\n", - "# Show the plot\n", - "plt.show()\n", + "# Save the data to a CSV file\n", + "data = pd.concat([meta_data['Close'], tesla_data['Close']], axis=1)\n", + "data.columns = ['META', 'TESLA']\n", + "data.to_csv('stock_price_ytd.csv')\n", "```\n", "\n", - "Please save the above code in a file named `plot_stock_price_ytd.py` and execute it. The script will fetch the stock data, save it to `stock_price_ytd.csv`, plot the chart, and save the plot to `stock_price_ytd.png`. If `matplotlib` is not installed, you will need to install it first by running `pip install matplotlib`.\n", + "This code fetches the closing prices for META and TESLA for the current year, plots the closing prices, saves the plot to 'stock_price_ytd.png', and saves the closing prices to 'stock_price_ytd.csv'.\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[31m\n", - ">>>>>>>> EXECUTING CODE BLOCK 0 (inferred language is python)...\u001b[0m\n", + ">>>>>>>> EXECUTING CODE BLOCK (inferred language is python)...\u001b[0m\n", "\u001b[33muser_proxy\u001b[0m (to assistant):\n", "\n", "exitcode: 0 (execution succeeded)\n", - "Code output: \n", - "Figure(1000x500)\n", + "Code output: /Users/ekzhu/miniconda3/envs/autogen/lib/python3.11/site-packages/yfinance/utils.py:775: FutureWarning: The 'unit' keyword in TimedeltaIndex construction is deprecated and will be removed in a future version. Use pd.to_timedelta instead.\n", + " df.index += _pd.TimedeltaIndex(dst_error_hours, 'h')\n", + "\n", + "[*********************100%%**********************] 1 of 1 completed\n", + "/Users/ekzhu/miniconda3/envs/autogen/lib/python3.11/site-packages/yfinance/utils.py:775: FutureWarning: The 'unit' keyword in TimedeltaIndex construction is deprecated and will be removed in a future version. Use pd.to_timedelta instead.\n", + " df.index += _pd.TimedeltaIndex(dst_error_hours, 'h')\n", + "\n", + "[*********************100%%**********************] 1 of 1 completed\n", "\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33massistant\u001b[0m (to user_proxy):\n", "\n", - "The script has successfully executed and created a chart showing the stock price change YTD for META and TESLA. It has also saved the data to `stock_price_ytd.csv` and the plot to `stock_price_ytd.png`.\n", + "The code has successfully fetched the stock prices for META and TESLA, plotted the chart of their stock price change year-to-date (YTD), and saved the data to 'stock_price_ytd.csv' and the plot to 'stock_price_ytd.png'.\n", "\n", - "You should now have a CSV file with the stock price data and a PNG image with the plotted chart. The chart is normalized to show the percentage change in stock prices from the beginning of the year, with the starting price indexed to 100 for comparison purposes.\n", + "You can now view the chart in the 'stock_price_ytd.png' file and the data in the 'stock_price_ytd.csv' file in your current working directory.\n", "\n", "TERMINATE\n", "\n", @@ -390,12 +430,12 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "metadata": {}, "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAA+gAAAH0CAYAAACuKActAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8g+/7EAAAACXBIWXMAAA9hAAAPYQGoP6dpAACsdUlEQVR4nOzdd3hTdf/G8Xe6d9oCXexdNsgSZIhUlgsFEQUF3PqgPxyo6OMeKG4coI8oLhwMFyqKDEFBNsiUVTZtKd175Pz+OG2gtqUF2iZt79d15Upyzsk5nxQCvfNdFsMwDERERERERETEoVwcXYCIiIiIiIiIKKCLiIiIiIiIOAUFdBEREREREREnoIAuIiIiIiIi4gQU0EVEREREREScgAK6iIiIiIiIiBNQQBcRERERERFxAgroIiIiIiIiIk5AAV1ERERERETECSigi4iIiIiIiDgBBXQRERERERERJ6CALiIiIiIiIuIEFNBFREREREREnIACuoiIiIiIiIgTUEAXERERERERcQIK6CIiIiIiIiJOQAFdRERERERExAkooIuIiIiIiIg4AQV0ERERERERESeggC4iIiIiIiLiBBTQRURERERERJyAArqIiIiIiIiIE1BAFxEREREREXECCugiIiIiIiIiTkABXURERERERMQJKKCLiIiIiIiIOAEFdBEREREREREnoIAuIiIiIiIi4gQU0EVEREREREScgAK6iIiIiIiIiBNQQBcRERERERFxAgroIiIiIiIiIk5AAV1ERERERETECSigi4iIiIiIiDgBBXQRERERERERJ6CALiIiIiIiIuIEFNBFREREREREnIACuoiI1GrLly/HYrEwb948h1x/9uzZWCwWDhw44JDrl8fFF19M+/btHV2GiIhIjaeALiIiVW7r1q2MHDmSxo0b4+XlRf369bn00kt56623ihz3wgsv8O233zqmyHI4cOAAFovFfnN1daVRo0ZcffXVbN682dHllSklJYWnn36aTp064efnh7e3N+3bt+fhhx/m2LFjji6vSuTm5tKhQweaN29OZmZmsf0HDhzAx8eHa6+9tsif9Zluy5cvL/Z3w93dnbp169K7d28effRRDh065IB3KyIizs5iGIbh6CJERKT2WLVqFQMGDKBRo0aMGzeOsLAwDh8+zF9//cW+ffvYu3ev/Vg/Pz9GjhzJ7NmzK62e5cuXM2DAAObOncvIkSPP6rUHDhygadOmXH/99QwbNoz8/Hx27tzJjBkzyM7O5q+//qJz585nPEd+fj65ubl4enpisVjO452cnf379xMVFcWhQ4e49tpr6dOnDx4eHvz999988cUXBAcHs3v3bsBsQY+Pj2fbtm1VVl9VWr16NRdddBGPPPIIL7zwQpF9l19+OX/88Qc7d+5kyZIlRfZ98sknLF68mE8//bTI9ksvvZTMzMwifzdsNhuJiYmsW7eOBQsWYLFYmDVrFqNHj6709yciItWHm6MLEBGR2uX555/HarWybt06AgMDi+yLi4tzTFHn6YILLmDs2LH25xdddBFXXnklM2bM4L333ivxNenp6fj6+uLq6oqrq2tVlQpAXl4e11xzDbGxsSxfvpw+ffoU2f/888/z0ksvVWlNjtSrVy/uvPNOXnnlFcaMGUO7du0AmD9/Pj/++CPvvvsu4eHhRf6MAf766y8WL15cbDtgH7Lw778bAAcPHmTQoEGMGzeONm3a0KlTp8p5YyIiUu2oi7uIiFSpffv20a5du2LhHCAkJMT+2GKxkJ6ezscff2zvJjx+/Hj7/k2bNjF06FACAgLw8/Nj4MCB/PXXX8XOmZSUxH333UeTJk3w9PSkQYMG3HTTTcTHx5daY3Z2NpdffjlWq5VVq1ad9Xu85JJLAIiOjgZOjTP//fffufvuuwkJCaFBgwZF9v17DPrPP/9M//798ff3JyAggO7duzNnzpwix6xZs4YhQ4ZgtVrx8fGhf//+/Pnnn2XWN3/+fLZs2cJjjz1WLJwDBAQE8PzzzxfbvmPHDgYMGICPjw/169dn2rRpRfbn5OTwxBNP0LVrV6xWK76+vvTt25dly5YVOa6w+/crr7zC+++/T/PmzfH09KR79+6sW7eu2HXnzp1L27Zt8fLyon379nzzzTeMHz+eJk2aFDnOZrPxxhtv0K5dO7y8vAgNDeWOO+4gMTGxzJ/J1KlTqVu3LnfeeSeGYZCWlsakSZPs4b0iNW7cmNmzZ5OTk1PsZygiIrWbWtBFRKRKNW7cmNWrV7Nt27YzTjz26aefcuutt9KjRw9uv/12AJo3bw7A9u3b6du3LwEBATz00EO4u7vz3nvvcfHFF/P777/Ts2dPANLS0ujbty87d+7k5ptv5oILLiA+Pp7vv/+eI0eOULdu3WLXzczM5KqrrmL9+vX89ttvdO/e/azf4759+wCoU6dOke1333039erV44knniA9Pb3U18+ePZubb76Zdu3aMWXKFAIDA9m0aROLFi3ihhtuAGDp0qUMHTqUrl278uSTT+Li4sJHH33EJZdcwsqVK+nRo0ep5//+++8BuPHGG8v9nhITExkyZAjXXHMNo0aNYt68eTz88MN06NCBoUOHAuaY9g8++IDrr7+e2267jdTUVGbNmsXgwYNZu3Ztse7+c+bMITU1lTvuuAOLxcK0adO45ppr2L9/P+7u7gD8+OOPXHfddXTo0IGpU6eSmJjILbfcQv369YvVeMcddzB79mwmTJjAvffeS3R0NG+//TabNm3izz//tJ+zJFarlenTp3PttdfywQcfsGPHDmJjY/n5558rZehBr169aN68OYsXL67wc4uISDVmiIiIVKFff/3VcHV1NVxdXY1evXoZDz30kPHLL78YOTk5xY719fU1xo0bV2z78OHDDQ8PD2Pfvn32bceOHTP8/f2Nfv362bc98cQTBmAsWLCg2DlsNpthGIaxbNkyAzDmzp1rpKamGv379zfq1q1rbNq0qcz3Eh0dbQDG008/bZw4ccKIiYkxli9fbnTp0sUAjPnz5xuGYRgfffSRARh9+vQx8vLyipyjcF90dLRhGIaRlJRk+Pv7Gz179jQyMzNLrNlmsxktW7Y0Bg8ebN9mGIaRkZFhNG3a1Lj00kvPWHeXLl0Mq9Va5vsr1L9/fwMwPvnkE/u27OxsIywszBgxYoR9W15enpGdnV3ktYmJiUZoaKhx880327cV/tzq1KljJCQk2Ld/9913BmD88MMP9m0dOnQwGjRoYKSmptq3LV++3ACMxo0b27etXLnSAIzPP/+8yPUXLVpU4vbSXH755YbVajVcXV2NKVOmnPHY//znP0Zpv0oVvseXX3651NdfddVVBmAkJyeXqzYREan51MVdRESq1KWXXsrq1au58sor2bJlC9OmTWPw4MHUr1/f3rJ7Jvn5+fz6668MHz6cZs2a2beHh4dzww038Mcff5CSkgKYXbk7derE1VdfXew8/24VTU5OZtCgQezatYvly5eXObnb6Z588knq1atHWFgYF198Mfv27eOll17immuuKXLcbbfdVuZ488WLF5OamsojjzyCl5dXiTVv3ryZPXv2cMMNN3Dy5Eni4+OJj48nPT2dgQMHsmLFCmw2W6nXSElJwd/fv9zvD8wJ+04fS+3h4UGPHj3Yv3+/fZurqyseHh6A2d08ISGBvLw8unXrxsaNG4ud87rrriMoKMj+vG/fvgD2cx47doytW7dy00034efnZz+uf//+dOjQoci55s6di9Vq5dJLL7X/POLj4+natSt+fn7FutmX5p133iEnJ4eGDRvy+OOPl+s156rwPaWmplbqdUREpPpQF3cREaly3bt3Z8GCBeTk5LBlyxa++eYbXn/9dUaOHMnmzZtp27Ztqa89ceIEGRkZtG7duti+Nm3aYLPZOHz4MO3atWPfvn2MGDGiXDVNmjSJrKwsNm3aZJ8krLxuv/12rr32WlxcXAgMDKRdu3Z4enoWO65p06Zlnquwe/yZuv/v2bMHgHHjxpV6THJycpHwe7qAgIAiwbo8GjRoUOxLjaCgIP7+++8i2z7++GNeffVVdu3aRW5urn17Se+9UaNGxc4H2MeMHzx4EIAWLVoUe22LFi2KhP49e/aQnJxcZB6D05V3AsJGjRoREhJCu3bt8Pb2LtdrzlVaWhrAWX9ZIiIiNZcCuoiIOIyHhwfdu3ene/futGrVigkTJjB37lyefPLJKq/lqquu4ssvv+TFF1/kk08+wcWl/J3MWrZsSVRUVJnHVVTgK2wdf/nll0tt6T+9xfnfIiMj2bRpE4cPH6Zhw4blumZpLf/Gaau1fvbZZ4wfP57hw4czefJkQkJCcHV1ZerUqfYvHs72nOVls9kICQnh888/L3F/vXr1zvqclW3btm2EhIQQEBDg6FJERMRJKKCLiIhT6NatGwDHjx+3bytpcq569erh4+PDP//8U2zfrl27cHFxsYfO5s2bl3vt7uHDhzNo0CDGjx+Pv78/M2bMOJe3cd4KJ8Lbtm1biS3Hpx8TEBBQri8G/u2KK67giy++4LPPPmPKlCnnXuy/zJs3j2bNmtnX+S50rl+4NG7cGIC9e/cW2/fvbc2bN+e3337joosuqvSW74qwevVq9u3bV+ISbSIiUntpDLqIiFSpZcuWldhC+tNPPwEU6bru6+tLUlJSkeNcXV0ZNGgQ3333XZGlyWJjY5kzZw59+vSxt0iOGDHC3oX+30qq4aabbmL69OnMnDmThx9++Fze3nkbNGgQ/v7+TJ06laysrCL7Cmvu2rUrzZs355VXXrF3kz7diRMnzniNkSNH0qFDB55//nlWr15dbH9qaiqPPfbYWdde2CJ++s92zZo1JV6jPCIiImjfvj2ffPJJkff5+++/s3Xr1iLHjho1ivz8fJ599tli58nLyyv298iRDh48yPjx4/Hw8GDy5MmOLkdERJyIWtBFRKRK3XPPPWRkZHD11VcTGRlJTk4Oq1at4quvvqJJkyZMmDDBfmzXrl357bffeO2114iIiKBp06b07NmT5557jsWLF9OnTx/uvvtu3NzceO+998jOzi6yrvTkyZOZN28e1157LTfffDNdu3YlISGB77//npkzZ9KpU6di9U2cOJGUlBQee+wxrFYrjz76aJX8XAoFBATw+uuvc+utt9K9e3duuOEGgoKC2LJlCxkZGXz88ce4uLjwwQcfMHToUNq1a8eECROoX78+R48eZdmyZQQEBPDDDz+Ueg13d3cWLFhAVFQU/fr1Y9SoUVx00UW4u7uzfft25syZQ1BQUIlroZ/J5ZdfzoIFC7j66qu57LLLiI6OZubMmbRt27bELxLK44UXXuCqq67ioosuYsKECSQmJvL222/Tvn37Iufs378/d9xxB1OnTmXz5s0MGjQId3d39uzZw9y5c3nzzTcZOXLkOdVwPjZu3Mhnn32GzWYjKSmJdevWMX/+fCwWC59++ikdO3as8ppERMSJOXIKeRERqX1+/vln4+abbzYiIyMNPz8/w8PDw2jRooVxzz33GLGxsUWO3bVrl9GvXz/D29vbAIosubZx40Zj8ODBhp+fn+Hj42MMGDDAWLVqVbHrnTx50pg4caJRv359w8PDw2jQoIExbtw4Iz4+3jCMosusne6hhx4yAOPtt98u9b2UZyktwzi1lNq6detK3Ve4zFqh77//3ujdu7fh7e1tBAQEGD169DC++OKLIsds2rTJuOaaa4w6deoYnp6eRuPGjY1Ro0YZS5YsOWM9hRITE40nnnjC6NChg+Hj42N4eXkZ7du3N6ZMmWIcP37cflz//v2Ndu3aFXv9uHHjiix1ZrPZjBdeeMFo3Lix4enpaXTp0sVYuHBhsePO9HMDjCeffLLIti+//NKIjIw0PD09jfbt2xvff/+9MWLECCMyMrLY699//32ja9euhre3t+Hv72906NDBeOihh4xjx46V62diGIbRuHFj47LLLivzuPIss1Z4c3NzM4KDg42ePXsaU6ZMMQ4ePFjuekREpPawGMY5zMQiIiIi4kCdO3emXr16LF682NGliIiIVBiNQRcRERGnlZubS15eXpFty5cvZ8uWLVx88cWOKUpERKSSqAVdREREnNaBAweIiopi7NixREREsGvXLmbOnInVamXbtm3UqVPH0SWKiIhUGE0SJyIiIk4rKCiIrl278sEHH3DixAl8fX257LLLePHFFxXORUSkxlELuoiIiIiIiIgT0Bh0ERERERERESeggC4iIiIiIiLiBDQGvZax2WwcO3YMf39/LBaLo8sREREREREHMQyD1NRUIiIicHFR260zUECvZY4dO0bDhg0dXYaIiIiIiDiJw4cP06BBA0eXISig1zr+/v6A+SEMCAhwcDUiIiIiIuIoKSkpNGzY0J4RxPEU0GuZwm7tAQEBCugiIiIiIqKhr05EAw1EREREREREnIACuoiIiIiIiIgTUEAXERERERERcQIagy4lys/PJzc319Fl1Bju7u64uro6ugwREREREXFiCuhShGEYxMTEkJSU5OhSapzAwEDCwsI0CYeIiIiIiJRIAV2KKAznISEh+Pj4KExWAMMwyMjIIC4uDoDw8HAHVyQiIiIiIs5IAV3s8vPz7eG8Tp06ji6nRvH29gYgLi6OkJAQdXcXEREREZFiNEmc2BWOOffx8XFwJTVT4c9VY/tFRERERKQkCuhSjLq1Vw79XEVERERE5EwU0EVEREREREScgAK6iIiIiIhIFTEMw9EliBNTQJcaYfz48VgsFu68885i+/7zn/9gsVgYP358kWP/fRsyZAjLly8vcd/pt+XLlwNw5MgRPDw8aN++fRW+UxERERGprrLz8hn30ToW74h1dCnipBTQpcZo2LAhX375JZmZmfZtWVlZzJkzh0aNGhU5dsiQIRw/frzI7YsvvqB3795Fto0aNarYsb179wZg9uzZjBo1ipSUFNasWVOl71VEREREqp+nf9jBit0nmDxvCylZmjhYitMya1JjXHDBBezbt48FCxYwZswYABYsWECjRo1o2rRpkWM9PT0JCwsr8Tynb/f29iY7O7vYsYZh8NFHH/Huu+/SoEEDZs2aRc+ePSv4HYmIiIhITfHVukPMWXMIiwXeuK4zAV7uji5JnJACupyRYRhk5uZX+XW93V3Padbzm2++mY8++sge0D/88EMmTJhg75ZeUZYtW0ZGRgZRUVHU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VruIiFSImrDEmoiIiDNTQC9FWFgYa9euJTIyssT9a9euJTS0/OumpqWlsXfvXvvz6OhoNm/eTHBwMHXq1OH555/nyiuvJDw8nPj4eN555x2OHj3KtddeC0CbNm0YMmQIt912GzNnziQ3N5eJEycyevTocs/gLiIicj7Ugi4iIlK5FNBL8eCDD3L77bezYcMGBg4caA/jsbGxLFmyhP/973+88sor5T7f+vXrGTBggP15YbfzcePGMXPmTHbt2sXHH39MfHw8derUoXv37qxcuZJ27drZX/P5558zceJEBg4ciIuLCyNGjGD69OkV9I5FRETOTC3oIiIilUvroJ/BV199xeuvv86GDRvIz88HwNXVla5du3L//fczatQoB1d49rTWoYiInIvcfBut//szNgPWPjqQkAAvR5ckIiLnSdnA+agF/Qyuu+46rrvuOnJzc4mPjwegbt26Fb7uuYiIiLOLSc7CZoCHqwt1/TS3iYiISGVQQC8Hd3d3goOD7Y9FRERqm8Lu7RGBXri4FJ9AVURERM6f1kE/g8WLFzNs2DCCgoLw8fHBx8eHoKAghg0bxm+//ebo8kRERKqMJogTERGpfAropfj4448ZNmwYVquV119/nYULF7Jw4UJef/11AgMDGTZsGJ9++qmjyxQREakSmiBORESk8qmLeymef/553njjDf7zn/8U2zd+/Hj69OnDM888w4033uiA6kRERKqWvQU90MfBlYiIiNRcakEvxaFDh4iKiip1/8CBAzly5EgVViQiIuI4x5JPjUEXERGRyqGAXop27doxa9asUvd/+OGHtG3btgorEhERcRyNQRcREal86uJeildffZXLL7+cRYsWERUVRWhoKACxsbEsWbKE/fv38+OPPzq4ShERkcpnGIZ9DHoDdXEXERGpNAropbj44ovZtm0bM2bM4K+//iImJgaAsLAwhg4dyp133kmTJk0cW6SIiEgViE/LITvPhsUCYVZ1cRcREaksCuhn0KRJE1566SVHlyEiIuJQha3nof5eeLhpdJyIiEhl0f+yIiIickbHkjRBnIiISFVQQD9HW7ZswdXV1dFliIiIVLpTE8Rp/LmIiEhlUkA/D4ZhOLoEERGRSlfYxb1+oGZwFxERqUwag16Ka6655oz7k5OTsVgsVVSNiIiI4xzREmsiIiJVQgG9FD/88AOXXnqpfXm1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++6TJk2aiK+vryQlJdX4fugBs1QX81QPs7RuPL7qYZbqYp7qYp7aYfZEd44NehVdvHhRBg0aJG5ubjJy5Ejp2LGjuLq6KoPOqVOnxMPDQ9555x0RESktLVW+193dXRYvXiwiIkVFRTJo0CBp1aqVfPXVVzW+H3rALNXFPNXDLK0bj696mKW6mKe6mKd2mD3R3WODXgVFRUUyduxYGT58uJw7d055PDg4WMaNGyciIgUFBTJnzhypU6eOpKWliciN62169uwpL730kvJ9hw8frsHq9YVZqot5qodZWjceX/UwS3UxT3UxT+0weyJ18Br0Kqhbty6cnZ0xbtw4tG7dGhUVFQCA0NBQpKSkQETQoEEDPP/883jooYfw7LPP4sKFCzAYDEhLS0NOTg4GDx6sPF/nzp012hPtMUt1MU/1MEvrxuOrHmapLuapLuapHWZPpA7eB72Kbr73ovn+jCNHjkS9evUQFRWlbJeRkYFevXqhoqICQUFBOHDgANq3b4/Vq1ejefPmWpWvK8xSXcxTPczSuvH4qodZqot5qot5aofZE909Nuh3oUePHnj55ZcxduxYGI1GAICdnR3Onj2LI0eOIDExEQEBARg7dqzGleofs1QX81QPs7RuPL7qYZbqYp7qYp7aYfZE1cMG/Q6dO3cOISEh2LFjhzIFp6ysDE5OThpXVvswS3UxT/UwS+vG46seZqku5qku5qkdZk9UfbwGvZrM72fs27cP9evXVwabmTNnIiIiAjk5OVqWV6swS3UxT/UwS+vG46seZqku5qku5qkdZk905xy0LqC2MRgMAIBDhw4hLCwM3333HSZMmIDi4mLExsbCzc1N4wprD2apLuapHmZp3Xh81cMs1cU81cU8tcPsie5CzS0Ybz1KSkqkTZs2YjAYxNnZWf7xj39oXVKtxSzVxTzVwyytG4+vepilupinupindpg90Z3hNeh36PHHH0fbtm2xaNEiuLi4aF1OrcYs1cU81cMsrRuPr3qYpbqYp7qYp3aYPVH1sUG/Q5WVlbC3t9e6DKvALNXFPNXDLK0bj696mKW6mKe6mKd2mD1R9bFBJyIiIiIiItIBruJOREREREREpANs0ImIiIiIiIh0gA06ERERERERkQ6wQSciIiIiIiLSATboRERERERERDrABp2IiIiIiIhIB9igExEREREREekAG3QiIqJaaty4cTAYDDAYDHB0dETz5s3x+OOPY8WKFTAajVV+npiYGDRq1OjeFUpERERVwgadiIioFhswYAAyMzORmpqKnTt34rHHHkNERAQGDRqEiooKrcsjIiKiamCDTkREVIs5OzvD3d0dHh4eeOihh/C3v/0NW7Zswc6dOxETEwMAWLRoETp27Ih69erB09MTr776KgoLCwEACQkJeOGFF5Cfn6+cjX///fcBAKWlpZg2bRo8PDxQr149dOnSBQkJCdrsKBERkQ1gg05ERGRlevfujYCAAGzcuBEAYGdnh2XLluGXX37B559/jt27d2P69OkAgJCQECxZsgQNGzZEZmYmMjMzMW3aNABAeHg4Dh48iK+++go//fQThg0bhgEDBuDMmTOa7RsREZE1M4iIaF0EERERVd+4ceOQl5eHzZs33/K1ESNG4KeffkJycvItX1u/fj0mTZqEy5cvAzBdg/76668jLy9P2SYtLQ0PPPAA0tLS0LJlS+Xxvn37Ijg4GHPnzlV9f4iIiGydg9YFEBERkfpEBAaDAQAQHx+PefPm4eTJkygoKEBFRQWuXbuG4uJi1K1b9w+//8SJE6isrES7du0sHi8tLUWTJk3uef1ERES2iA06ERGRFUpJSUHr1q2RmpqKQYMG4ZVXXsEHH3yAxo0bY9++fRg/fjzKysr+tEEvLCyEvb09jhw5Ant7e4uv1a9fvyZ2gYiIyOawQSciIrIyu3fvxokTJxAZGYkjR47AaDRi4cKFsLMzLT2zdu1ai+2dnJxQWVlp8VhgYCAqKyuRk5ODRx55pMZqJyIismVs0ImIiGqx0tJSZGVlobKyEtnZ2YiLi8O8efMwaNAgjBkzBj///DPKy8vx0Ucf4cknn8T+/fvx2WefWTyHt7c3CgsLsWvXLgQEBKBu3bpo164dRo4ciTFjxmDhwoUIDAzEpUuXsGvXLvj7+2PgwIEa7TEREZH14iruREREtVhcXBxatGgBb29vDBgwAHv27MGyZcuwZcsW2NvbIyAgAIsWLcL8+fPh5+eHVatWYd68eRbPERISgkmTJmH48OFo1qwZFixYAABYuXIlxowZgzfeeAMPPvggBg8ejKSkJHh5eWmxq0RERFaPq7gTERERERER6QDPoBMRERERERHpABt0IiIiIiIiIh1gg05ERERERESkA2zQiYiIiIiIiHSADToRERERERGRDrBBJyIiIiIiItIBNuhEREREREREOsAGnYiIiIiIiEgH2KATERERERER6QAbdCIiIiIiIiIdYINOREREREREpANs0ImIiIiIiIh0gA06ERERERERkQ6wQSciIiIiIiLSATboRERERERERDrABp2IiIiIiIhIB9igExEREREREekAG3QiIiIiIiIiHWCDTkRERERERKQDbNCJiIiIiIiIdIANOhEREREREZEO/H8SLfcXJN0ADwAAAABJRU5ErkJggg==", + "image/png": "iVBORw0KGgoAAAANSUhEUgAABXgAAAK8CAYAAABV1dcbAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/H5lhTAAAACXBIWXMAAA9hAAAPYQGoP6dpAADYJ0lEQVR4nOzdd3hUBdrG4WdmMumNdEoIJNRQBAEhIEhHQERFRSyIulasuOqHriuKbV27WFBRLBRF0VVEpUiR3mvoPZCekF4mM+f7IxCJoSQhZBLyu68rV5JT3zMcIzx55z0mwzAMAQAAAAAAAABqHbOzCwAAAAAAAAAAVA4BLwAAAAAAAADUUgS8AAAAAAAAAFBLEfACAAAAAAAAQC1FwAsAAAAAAAAAtRQBLwAAAAAAAADUUgS8AAAAAAAAAFBLEfACAAAAAAAAQC1FwAsAAAAAAAAAtRQBLwAAAAAAAADUUgS8AAAAAAAAAFBLEfACAAAAAAAAQC1FwAsAAAAAAAAAtRQBLwAAAAAAAADUUgS8AAAAAAAAAFBLEfACAAAAAAAAQC1FwAsAAAAAAAAAtRQBLwAAAAAAAADUUgS8AAAAAAAAAFBLEfACAAAAAAAAQC1FwAsAAAAAAAAAtRQBLwAAAAAAAADUUgS8AAAAAAAAAFBLEfACAAAAAAAAQC1FwAsAAAAAAAAAtRQBLwAAAAAAAADUUgS8AAAAAAAAAFBLEfACAAAAAAAAQC1FwAsAAAAAAAAAtRQBLwAAAAAAAADUUgS8AAAAAAAAAFBLEfACAAAAAAAAQC1FwAsAAAAAAAAAtRQBLwAAAAAAAADUUgS8AAAAAAAAAFBLEfACAAAAAAAAQC1FwAsAAAAAAAAAtRQBLwAAAAAAAADUUgS8AAAAAAAAAFBLEfACAAAAAAAAQC1FwAsAAAAAAAAAtRQBLwAAAAAAAADUUgS8AAAAAAAAAFBLEfACAAAAAAAAQC1FwAsAAAAAAAAAtRQBLwAAAAAAAADUUgS8AAAAAAAAAFBLEfACAAAAAAAAQC1FwAsAAAAAAAAAtRQBLwAAAAAAAADUUgS8AAAAAAAAAFBLEfACAAAAAAAAQC1FwAsAAAAAAAAAtRQBLwAAAAAAAADUUgS8AAAAAAAAAFBLEfACAAAAAAAAQC1FwAsAAAAAAAAAtRQBLwAAAAAAAADUUgS8AAAAAAAAAFBLEfACAAAAAAAAQC1FwAsAAAAAAAAAtRQBLwAAAFBLjRkzRt7e3k47f+/evdW7d2+nnR8AAAAEvAAAAGc1depUmUwmmUwmLVu2rMx6wzAUHh4uk8mkq666qtS6k/ud7uO+++7T4sWLz7rNqR+nuvHGG2UymfTUU09d0Gsvr969e5frGiZMmCBJatKkyRm3ufLKK0sde9myZRo8eLAaNmwod3d3NW7cWMOGDdP06dNLbWcymfTggw+Wu+YPPvhAJpNJXbt2rdC1OhwOffnll+ratasCAgLk4+OjFi1aaPTo0Vq1alXJdrGxsZowYYIOHjxYoeNXpwkTJpR67T09PRUdHa1//etfyszMdHZ5ZdhsNrVr105RUVHKy8srs/7gwYPy9PTUDTfcUO7/rhYvXqyDBw+WWma1WhUUFKTu3bvr6aef1uHDh51wtQAAAOXn4uwCAAAAagN3d3dNnz5dl19+eanlS5YsUVxcnNzc3E6734ABAzR69Ogyy1u0aKGIiAh99dVXpZaPHz9e3t7eeuaZZ057vMzMTP38889q0qSJZsyYoVdffbVMAFzdnnnmGf3jH/8o+X7t2rV699139fTTT6t169Yly9u3b1/ydYcOHfT444+XOVaDBg1Kvp41a5ZGjhypDh066JFHHlG9evV04MABLV26VJ988oluvvnmStc8bdo0NWnSRGvWrNHevXvVrFmzcu338MMP6/3339fw4cN1yy23yMXFRbt27dKvv/6qyMhIdevWTVJxwPv888+rd+/eatKkSaXrrA4ffvihvL29lZ2drXnz5umll17SH3/8oeXLl5/z3po3b141VSlZrVZ9/PHH6tGjhyZOnKiXX3651PoHH3xQrq6uevfddzV8+PBS67788kvNnz+/zH9vrVu3LgmLR40apSFDhsjhcCg9PV1r167V22+/rXfeeUdTpkzRTTfddGEvEAAAoLIMAAAAnNHnn39uSDKuu+46IygoyLDZbKXW33333UanTp2MiIgIY+jQoaXWSTLGjh1bofO1adPGuOKKK864/rPPPjOsVqvxxx9/GJKMxYsXV+j41WHWrFmGJGPRokWnXX+61+p0oqOjjTZt2hgFBQVl1iUmJpb6viKv9f79+w1JxuzZs43g4GBjwoQJ5dovISHBMJlMxt13311mncPhKFXTuV6DqnL77bcbXl5eldr3ueeeMyQZycnJpZZfd911hiRjxYoVZ9w3JyenUuesCvfff79htVqNbdu2lSz77rvvDEnGBx98cNp9xo4da5zpnz4HDhwwJBn//e9/y6w7ePCg0aJFC8PV1dXYtGlT1VwAAABAFWNEAwAAQDmMGjVKqampmj9/fsmywsJCfffdd+fVSVpR06ZN04ABA9SnTx+1bt1a06ZNO+c+NptNAQEBuuOOO8qsy8zMlLu7u/75z3+WLHvvvffUpk0beXp6ql69eurcuXOZkQjVYd++ferSpYtcXV3LrAsJCan0cadNm6Z69epp6NChuv7668v1GkrSgQMHZBiGevToUWadyWQqqWnq1Km64YYbJEl9+vQpNQ7gpA8++EBt2rSRm5ubGjRooLFjx+r48eNljrt69WoNGTJE9erVk5eXl9q3b6933nnnrHVu2rRJwcHB6t27t7Kzs8t1bafq27dvyfVKxSM42rZtq/Xr16tXr17y9PTU008/XbLu7zN48/PzNWHCBLVo0ULu7u6qX7++rrvuOu3bt69kG4fDobfffltt2rSRu7u7QkNDde+99yo9Pf2c9b3yyisKCgrSfffdJ8MwlJ2drUcffVQxMTG67777Kny9ZxMREaGpU6eqsLBQr732WpUeGwAAoKoQ8AIAAJRDkyZNFBMToxkzZpQs+/XXX5WRkXHWt27n5+crJSWlzEdhYWGFazh27JgWLVqkUaNGSSoOnb/77rtzHstqteraa6/Vjz/+WGbbH3/8UQUFBSXX8Mknn+jhhx9WdHS03n77bT3//PPq0KGDVq9eXeF6z8Zms532dTl1tmpERIQWLlyouLi4Kj33tGnTdN1118nV1VWjRo3Snj17tHbt2nPuFxERIal4dERubu4Zt+vVq5cefvhhSdLTTz+tr776Sl999VXJuIoJEyZo7NixatCggd544w2NGDFCkydP1sCBA2Wz2UqOM3/+fPXq1UuxsbF65JFH9MYbb6hPnz6aM2fOGc+9du1a9e3bVx07dtSvv/5aqQewnQxiAwMDS5alpqZq8ODB6tChg95++2316dPntPva7XZdddVVev7559WpUye98cYbeuSRR5SRkaFt27aVbHfvvffqiSeeUI8ePfTOO+/ojjvu0LRp0zRo0KBSr8Hp+Pn56d1339WyZcv06aef6tlnn1ViYqI+/vjjCzKuJCYmRlFRUaV+uQMAAFCjOLuFGAAAoCY7OaJh7dq1xqRJkwwfHx8jNzfXMAzDuOGGG4w+ffoYhnH6sQOSzvgxY8aM057vbCMaXn/9dcPDw8PIzMw0DMMwdu/ebUgyfvjhh3Nex++//25IMn7++edSy4cMGWJERkaWfD98+HCjTZs25zze2ZRnRMOZXpdXXnmlZLspU6YYkgxXV1ejT58+xrPPPmv8+eefht1uL3NMlXNEw7p16wxJxvz58w3DKB6t0KhRI+ORRx4p17WNHj3akGTUq1fPuPbaa43XX3/d2LFjR7lfg6SkJMPV1dUYOHBgqeuYNGmSIcn47LPPDMMwjKKiIqNp06ZGRESEkZ6eXuoYDoej5OtTRzQsW7bM8PX1NYYOHWrk5+ef81pOjmjYtWuXkZycbBw4cMCYPHmy4ebmZoSGhpaMYbjiiisMScZHH31U5hhXXHFFqfv1s88+MyQZb775ZpltT9b9559/GpKMadOmlVr/22+/nXb5mVx11VWGn5+fYbFYjPHjx59128qOaDhp+PDhhiQjIyOjXLUBAABUJzp4AQAAyunGG29UXl6e5syZo6ysLM2ZM+ec4xmGDx+u+fPnl/k4Uwfk2UybNk1Dhw6Vj4+PJKl58+bq1KlTuUYM9O3bV0FBQfrmm29KlqWnp2v+/PkaOXJkyTJ/f3/FxcWVq6P1fHTt2vW0r8vJ7mRJuvPOO/Xbb7+pd+/eWrZsmSZOnKiePXuqefPmWrFiRaXOO23aNIWGhpa8/iaTSSNHjtTMmTNlt9vPuf/nn3+uSZMmqWnTpvrhhx/0z3/+U61bt1a/fv109OjRc+6/YMECFRYW6tFHH5XZ/Ndfxe+++275+vrql19+kSRt3LhRBw4c0KOPPip/f/9Sxzhdl+qiRYs0aNAg9evXT7Nnzz7jQ/9Op2XLlgoODlbTpk117733qlmzZvrll1/k6elZso2bm9tpR3z83ffff6+goCA99NBDZdadrHvWrFny8/PTgAEDSnVvd+rUSd7e3lq0aFG56n7//fdVWFio8PBwPfvss+W82so52QmdlZV1Qc8DAABQGS7OLgAAAKC2CA4OVv/+/TV9+nTl5ubKbrfr+uuvP+s+jRo1Uv/+/c/73Dt27NDGjRs1evRo7d27t2R579699f777yszM1O+vr5n3N/FxUUjRozQ9OnTVVBQIDc3N82ePVs2m61UwPvUU09pwYIFuuyyy9SsWTMNHDhQN99882nnzp6PoKCgcr0ugwYN0qBBg5Sbm6v169frm2++0UcffaSrrrpKO3furNAsXrvdrpkzZ6pPnz4l82Wl4rD5jTfe0MKFCzVw4MCzHsNsNmvs2LEaO3asUlNTtXz5cn300Uf69ddfddNNN+nPP/886/6HDh2SVByqnsrV1VWRkZEl60+OSWjbtu05rys/P19Dhw5Vp06d9O2338rFpWJ/xf/+++/l6+srq9WqRo0aKSoqqsw2DRs2PO0s5L/bt2+fWrZsedYa9uzZo4yMjDP+2SUlJZWr7saNGyskJERt2rSRh4dHufaprJOzjE/+cgUAAKAmIeAFAACogJtvvll33323EhISNHjw4DLdlRfK119/LUl67LHH9Nhjj5VZ//3335+zw/Kmm27S5MmT9euvv+qaa67Rt99+q1atWumSSy4p2aZ169batWuX5syZo99++03ff/+9PvjgA/373//W888/X7UXVQGenp7q2bOnevbsqaCgID3//PP69ddfdfvtt5f7GH/88Yfi4+M1c+ZMzZw5s8z6adOmnTPgPVVgYKCuvvpqXX311erdu7eWLFmiQ4cOlczqrS5ubm4aMmSI/ve//+m3337TVVddVaH9e/XqpaCgoLNuU5UBqsPhUEhIyBk7z4ODg6vsXFVl27ZtCgkJOesvUQAAAJyFgBcAAKACrr32Wt17771atWpVqXEHF5JhGJo+fbr69OmjBx54oMz6iRMnatq0aecMeHv16qX69evrm2++0eWXX64//vhDzzzzTJntvLy8NHLkSI0cOVKFhYW67rrr9NJLL2n8+PFyd3evsuuqrM6dO0uS4uPjK7TftGnTFBISovfff7/MutmzZ+uHH37QRx99VKkws3PnzlqyZIni4+MVERFxxod9nQx/d+3apcjIyJLlhYWFOnDgQElX88ku2m3btp2z09lkMmnatGkaPny4brjhBv3666/q3bt3ha+hKkRFRWn16tWy2WyyWq1n3GbBggXq0aPHBe+8rQorV67Uvn37dOuttzq7FAAAgNNiBi8AAEAFeHt768MPP9SECRM0bNiwajnn8uXLdfDgQd1xxx26/vrry3yMHDlSixYt0rFjx856HLPZrOuvv14///yzvvrqKxUVFZUazyBJqamppb53dXVVdHS0DMOQzWar8ms7m4UL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5+dr+vTpaty4caltr7zySn3++eellrm5ucnLy0vx8fElyx555BFlZmaW2jYgIECSNHXqVN14441aunSpVq9era5du16oSwMAAAAAAEAVI+AFaphLL71U+/bt0+zZs3XLLbdIkmbPnq3GjRuradOmpbZ1c3NTWFjYaY9z6nIPDw8VFBSU2dYwDH3++ef64IMP1KhRI02ZMoWAFwAAAAAAoBZhRAPqBMMwlFtYVG0feYV25RYWyTCMStV75513luq2/eyzz3THHXdU1ctRYtGiRcrNzVX//v116623aubMmcrJyany8wAAAAAAAODCoIMXdUKeza7of/9e7eeNfWGQPF0r/p/ZrbfeqvHjx+vQoUOSpOXLl2vmzJklc3NPmjNnjry9vUste/rpp/X000+X6zxTpkzRTTfdJIvForZt2yoyMlKzZs0qmfMLAAAAAACAmo2AF6iBgoODNXToUE2dOlWGYWjo0KEKCgoqs12fPn304Ycfllp2crbuuRw/flyzZ8/WsmXLSpbdeuutmjJlCgEvAAAAAABALUHAizrBw2pR7AuDquVcDodDWZlZ8vH1kYfVUunj3HnnnXrwwQclSe+///5pt/Hy8lKzZs0qdfzp06crPz+/1MxdwzDkcDi0e/dutWjRolLHBQAAAAAAQPUh4EWdYDKZKjUqoTIcDoeKXC3ydHWRyWSq9HGuvPJKFRYWymQyadCgqg+np0yZoscff7xMt+4DDzygzz77TK+++mqVnxMAAAAAAABVi4AXqKEsFot27NhR8vXpFBQUKCEhodQyFxeX045zONWmTZu0YcMGTZs2Ta1atSq1btSoUXrhhRf04osvysWFHxEAAAAAAAA1mdnZBQA4M19fX/n6+p5x/W+//ab69euX+rj88svPedwpU6YoOjq6TLgrSddee62SkpI0d+7c86odAAAAAFA7GIah2Rvi9N36OGXl25xdDoAKoj0PqEGmTp161vU//vhjqW3Ptf2Zjvvee++dcduwsDDZ7fZyHRcAAAAAUPst3ZOicd9uliQ984NZA6JDdd2lDdWzebCsFnoDgZqOgBcAAAAAAKAO+2TpfkmSj5uLsgqKNGdLvOZsiVegl6uGXdJAIy5tpLYNfc/rOTMALhwCXgAAAAAAgDpq+7EMLdubIovZpF8f7am0nELN3nBUP28+ptScQk1dcVBTVxxUsxBv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VmRkpPXUU09dcI3c3Fxr3bp1hbahQ4daTZo0sdatW2edOnXqorVFR0db//znP/P3nThxokwXWTt3AYhRo0axyNpleOp4OdfFFlkrSo8ePaxbb721xM+B0qsMY3T//v1Wo0aNrLvvvtvKyckp1n0//fTTVsuWLQvtu+eee1gM5DI8dbyc62ILHJ0vJyfHatKkifXEE0+U+Dk8SWUYU+fbs2ePJclauHDhRc/hNah0PHW8nIvXINfm7mN0z549ha47e/ZsS5L1zTffWPv27bvoffOaBnguAl64raFDh1phYWHWokWLrKSkpPwtIyMj/5xHHnnEql27trVgwQLr999/t+Li4qy4uLj84+vWrbOqV69u3XfffYWukZKSctHnLc7KqJZlWa+99poVHh5uTZs2zVq7dq3Vr18/q169etbp06fzz9mzZ4+1atUqa8yYMVZwcLC1atUqa9WqVdbJkycvet3U1FQrKirKuv/++63169dbkydPtgIDA60PPvgg/5ysrKz8a8XExFh/+ctfrFWrVlnbtm27bN2VlaeOF8uyrA0bNlirVq2ybr75ZqtLly75j8vzxhtvWFOnTrW2bdtmrVu3znr88cctLy8va968eZetG2XH3cfo/v37rYYNG1rdu3e39u/fX+j5L2Xnzp1WYGCg9dRTT1mbNm2yxo8fb3l7e1uzZs3KP+fkyZP541aS9e9//9tatWqVtWfPnsvWXVl56nixLCt/LLRv394aOHCgtWrVKmvDhg35x8eMGWPNnj3b2rFjh5WYmGjdfffdlr+/f6FzcCF3H1O//PKL9c4771irVq2ydu/ebc2fP9/q2LGj1aBBAyszM/Oi1+U1qHQ8dbxYFq9B7sLdx+j5du3aZUkq9N/wReE1DfBcBLxwW5KK3CZMmJB/zunTp61HH33UqlKlihUYGGjdeuuthf7n8YUXXijyGnXq1Lno8xb3l7bD4bD+9re/WVFRUZafn5/VvXt3a8uWLYXOGTx4cJHPf7mZA2vWrLGuv/56y8/Pz6pZs6b12muvFTqe9x8A52833njjZeuurDx5vNSpU6fIx+V5/fXXrQYNGlj+/v5WRESE1aVLF2vBggWXrRlly93H6IQJEy56D5ezcOFCq23btpavr69Vv379Qvecd7yo6w4ePPiy166sPHm8XK7mESNGWLVr17Z8fX2tqKgoq0+fPtbKlSsve11P5+5jau3atVbXrl2tiIgIy8/Pz6pbt671yCOPWPv377/stXkNKjlPHi+8BrkHdx+j5ytuwGtZvKYBnspmWWe7yAMAAAAAAAAA3IqXswsAAAAAAAAAAJQOAS8AAAAAAAAAuCkCXgAAAAAAAABwUwS8AAAAAAAAAOCmCHgBAAAAAAAAwE0R8AIAAAAAAACAmyLgBQAAAAAAAAA3RcALAAAAAAAAAG6KgBcAAAAeYciQIbLZbLLZbLLb7YqKilLPnj31ySefyOFwFPs6EydOVHh4ePkVCgAAAJQAAS8AAAA8Rq9evZSUlKTdu3dr5syZ6tq1qx5//HHddNNNysnJcXZ5AAAAQIkR8AIAAMBj+Pn5KTo6WjVr1tRVV12lZ599VtOmTdPMmTM1ceJESdK///1vtWrVSkFBQYqNjdWjjz6qU6dOSZIWLVqkP/zhDzpx4kT+bOAXX3xRkpSVlaW//OUvqlmzpoKCgnTttddq0aJFzrlRAAAAeAwCXgAAAHi0bt26qU2bNpoyZYokycvLS2+//bY2bNigTz/9VAsWLNDTTz8tSerYsaPefPNNhYaGKikpSUlJSfrLX/4iSRo+fLiWL1+uyZMna+3atbrjjjvUq1cvbdu2zWn3BgAAgMrPZlmW5ewiAAAAgPI2ZMgQpaamaurUqRccu/vuu7V27Vpt3LjxgmPffPONHnnkER05ckSS6cE7YsQIpaam5p+zd+9e1a9fX3v37lWNGjXy9/fo0UPXXHONXn311TK/HwAAAECSfJxdAAAAAOBslmXJZrNJkubNm6exY8dq8+bNSktLU05OjjIzM5WRkaHAwMAiH79u3Trl5uaqcePGhfZnZWWpatWq5V4/AAAAPBcBLwAAADzepk2bVK9ePe3evVs33XSThg4dqldeeUURERH6+eef9cADD+jMmTMXDXhPnTolb29vJSYmytvbu9Cx4ODgirgFAAAAeCgCXgAAAHi0BQsWaN26dXriiSeUmJgoh8Ohf/3rX/LyMstVfPXVV4XO9/X1VW5ubqF97dq1U25urlJSUnTDDTdUWO0AAAAAAS8AAAA8RlZWlpKTk5Wbm6tDhw5p1qxZGjt2rG666SYNGjRI69evV3Z2tt555x3dfPPNWrp0qd5///1C16hbt65OnTql+fPnq02bNgoMDFTjxo117733atCgQfrXv/6ldu3a6fDhw5o/f75at26tvn37OumOAQAAUNl5ObsAAAAAoKLMmjVLMTExqlu3rnr16qWFCxfq7bff1rRp0+Tt7a02bdro3//+t15//XW1bNlSX3zxhcaOHVvoGh07dtQjjzyiu+66S9WrV9e4ceMkSRMmTNCgQYP05JNPqkmTJurfv79+++031a5d2xm3CgAAAA9hsyzLcnYRAAAAAAAAAICSYwYvAAAAAAAAALgpAl4AAAAAAAAAcFMEvAAAAAAAAADgpgh4AQAAAAAAAMBNEfACAAAAAAAAgJsi4AUAAAAAAAAAN0XACwAAAAAAAABuioAXAAAAAAAAANwUAS8AAAAAAAAAuCkCXgAAAAAAAABwUwS8AAAAAAAAAOCmCHgBAAAAAAAAwE0R8AIAAAAAAACAmyLgBQAAAAAAAAA3RcALAAAAAAAAAG6KgBcAAAAAAAAA3BQBLwAAAAAAAAC4KQJeAAAAAAAAAHBTBLwAAAAAAAAA4KYIeAEAAAAAAADATRHwAgAAAAAAAICbIuAFAAAAAAAAADdFwAsAAAAAAAAAboqAFwAAAAAAAADcFAEvAAAAAAAAALgpAl4AAAAAAAAAcFMEvAAAAAAAAADgpgh4AQAAAAAAAMBNEfACAAAAAAAAgJsi4AUAAAAAAAAAN0XACwAAAAAAAABuioAXAAAAAAAAANwUAS8AAAAAAAAAuCkCXgAAAAAAAABwUwS8AAAAAAAAAOCmCHgBAAAAAAAAwE39PxUbywGxcgDbAAAAAElFTkSuQmCC", "text/plain": [ "" ] @@ -421,14 +461,14 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "['Date', 'META', 'TSLA']\n", + "['Date', 'META', 'TESLA']\n", "['2024-01-02', '346.2900085449219', '248.4199981689453']\n", "['2024-01-03', '344.4700012207031', '238.4499969482422']\n", "['2024-01-04', '347.1199951171875', '237.92999267578125']\n", @@ -470,7 +510,34 @@ "['2024-02-27', '487.04998779296875', '199.72999572753906']\n", "['2024-02-28', '484.0199890136719', '202.0399932861328']\n", "['2024-02-29', '490.1300048828125', '201.8800048828125']\n", - "['2024-03-01', '502.29998779296875', '202.63999938964844']\n" + "['2024-03-01', '502.29998779296875', '202.63999938964844']\n", + "['2024-03-04', '498.19000244140625', '188.13999938964844']\n", + "['2024-03-05', '490.2200012207031', '180.74000549316406']\n", + "['2024-03-06', '496.0899963378906', '176.5399932861328']\n", + "['2024-03-07', '512.1900024414062', '178.64999389648438']\n", + "['2024-03-08', '505.95001220703125', '175.33999633789062']\n", + "['2024-03-11', '483.5899963378906', '177.77000427246094']\n", + "['2024-03-12', '499.75', '177.5399932861328']\n", + "['2024-03-13', '495.57000732421875', '169.47999572753906']\n", + "['2024-03-14', '491.8299865722656', '162.5']\n", + "['2024-03-15', '484.1000061035156', '163.57000732421875']\n", + "['2024-03-18', '496.9800109863281', '173.8000030517578']\n", + "['2024-03-19', '496.239990234375', '171.32000732421875']\n", + "['2024-03-20', '505.5199890136719', '175.66000366210938']\n", + "['2024-03-21', '507.760009765625', '172.82000732421875']\n", + "['2024-03-22', '509.5799865722656', '170.8300018310547']\n", + "['2024-03-25', '503.0199890136719', '172.6300048828125']\n", + "['2024-03-26', '495.8900146484375', '177.6699981689453']\n", + "['2024-03-27', '493.8599853515625', '179.8300018310547']\n", + "['2024-03-28', '485.5799865722656', '175.7899932861328']\n", + "['2024-04-01', '491.3500061035156', '175.22000122070312']\n", + "['2024-04-02', '497.3699951171875', '166.6300048828125']\n", + "['2024-04-03', '506.739990234375', '168.3800048828125']\n", + "['2024-04-04', '510.9200134277344', '171.11000061035156']\n", + "['2024-04-05', '527.3400268554688', '164.89999389648438']\n", + "['2024-04-08', '519.25', '172.97999572753906']\n", + "['2024-04-09', '516.9000244140625', '176.8800048828125']\n", + "['2024-04-10', '519.8300170898438', '171.75999450683594']\n" ] } ], @@ -502,7 +569,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 11, "metadata": {}, "outputs": [ { @@ -513,7 +580,7 @@ "\n", "Analyze the data and write a brief but engaging blog post. \n", " Data: \n", - "Date,META,TSLA\n", + "Date,META,TESLA\n", "2024-01-02,346.2900085449219,248.4199981689453\n", "2024-01-03,344.4700012207031,238.4499969482422\n", "2024-01-04,347.1199951171875,237.92999267578125\n", @@ -556,124 +623,134 @@ "2024-02-28,484.0199890136719,202.0399932861328\n", "2024-02-29,490.1300048828125,201.8800048828125\n", "2024-03-01,502.29998779296875,202.63999938964844\n", + "2024-03-04,498.19000244140625,188.13999938964844\n", + "2024-03-05,490.2200012207031,180.74000549316406\n", + "2024-03-06,496.0899963378906,176.5399932861328\n", + "2024-03-07,512.1900024414062,178.64999389648438\n", + "2024-03-08,505.95001220703125,175.33999633789062\n", + "2024-03-11,483.5899963378906,177.77000427246094\n", + "2024-03-12,499.75,177.5399932861328\n", + "2024-03-13,495.57000732421875,169.47999572753906\n", + "2024-03-14,491.8299865722656,162.5\n", + "2024-03-15,484.1000061035156,163.57000732421875\n", + "2024-03-18,496.9800109863281,173.8000030517578\n", + "2024-03-19,496.239990234375,171.32000732421875\n", + "2024-03-20,505.5199890136719,175.66000366210938\n", + "2024-03-21,507.760009765625,172.82000732421875\n", + "2024-03-22,509.5799865722656,170.8300018310547\n", + "2024-03-25,503.0199890136719,172.6300048828125\n", + "2024-03-26,495.8900146484375,177.6699981689453\n", + "2024-03-27,493.8599853515625,179.8300018310547\n", + "2024-03-28,485.5799865722656,175.7899932861328\n", + "2024-04-01,491.3500061035156,175.22000122070312\n", + "2024-04-02,497.3699951171875,166.6300048828125\n", + "2024-04-03,506.739990234375,168.3800048828125\n", + "2024-04-04,510.9200134277344,171.11000061035156\n", + "2024-04-05,527.3400268554688,164.89999389648438\n", + "2024-04-08,519.25,172.97999572753906\n", + "2024-04-09,516.9000244140625,176.8800048828125\n", + "2024-04-10,519.8300170898438,171.75999450683594\n", "\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33massistant\u001b[0m (to user_proxy):\n", "\n", - "To write a blog post, we need to analyze the data to identify trends, significant changes, and any other notable points. We will start by calculating the percentage change for both META and TSLA stocks from the beginning to the end of the data provided. This will give us an idea of the overall performance of each stock over the period.\n", + "To analyze this data and write a blog post, we first need to load the data into a pandas DataFrame, then perform some basic analysis on it. We can then use the results of this analysis to write the blog post.\n", "\n", - "Let's write a Python script to calculate the percentage change for both stocks.\n", + "Here is the Python code to load the data and perform the analysis:\n", "\n", "```python\n", - "# filename: stock_analysis.py\n", - "\n", + "# Python code\n", "import pandas as pd\n", "from io import StringIO\n", "\n", - "# Data provided as a CSV string\n", "data = \"\"\"\n", - "Date,META,TSLA\n", + "Date,META,TESLA\n", "2024-01-02,346.2900085449219,248.4199981689453\n", "2024-01-03,344.4700012207031,238.4499969482422\n", - "2024-01-04,347.1199951171875,237.92999267578125\n", - "2024-01-05,351.95001220703125,237.49000549316406\n", - "2024-01-08,358.6600036621094,240.4499969482422\n", - "2024-01-09,357.42999267578125,234.9600067138672\n", - "2024-01-10,370.4700012207031,233.94000244140625\n", - "2024-01-11,369.6700134277344,227.22000122070312\n", - "2024-01-12,374.489990234375,218.88999938964844\n", - "2024-01-16,367.4599914550781,219.91000366210938\n", - "2024-01-17,368.3699951171875,215.5500030517578\n", - "2024-01-18,376.1300048828125,211.8800048828125\n", - "2024-01-19,383.45001220703125,212.19000244140625\n", - "2024-01-22,381.7799987792969,208.8000030517578\n", - "2024-01-23,385.20001220703125,209.13999938964844\n", - "2024-01-24,390.70001220703125,207.8300018310547\n", - "2024-01-25,393.17999267578125,182.6300048828125\n", - "2024-01-26,394.1400146484375,183.25\n", - "2024-01-29,401.0199890136719,190.92999267578125\n", - "2024-01-30,400.05999755859375,191.58999633789062\n", - "2024-01-31,390.1400146484375,187.2899932861328\n", - "2024-02-01,394.7799987792969,188.86000061035156\n", - "2024-02-02,474.989990234375,187.91000366210938\n", - "2024-02-05,459.4100036621094,181.05999755859375\n", - "2024-02-06,454.7200012207031,185.10000610351562\n", - "2024-02-07,469.5899963378906,187.5800018310547\n", - "2024-02-08,470.0,189.55999755859375\n", - "2024-02-09,468.1099853515625,193.57000732421875\n", - "2024-02-12,468.8999938964844,188.1300048828125\n", - "2024-02-13,460.1199951171875,184.02000427246094\n", - "2024-02-14,473.2799987792969,188.7100067138672\n", - "2024-02-15,484.0299987792969,200.4499969482422\n", - "2024-02-16,473.32000732421875,199.9499969482422\n", - "2024-02-20,471.75,193.75999450683594\n", - "2024-02-21,468.0299987792969,194.77000427246094\n", - "2024-02-22,486.1300048828125,197.41000366210938\n", - "2024-02-23,484.0299987792969,191.97000122070312\n", - "2024-02-26,481.739990234375,199.39999389648438\n", - "2024-02-27,487.04998779296875,199.72999572753906\n", - "2024-02-28,484.0199890136719,202.0399932861328\n", - "2024-02-29,490.1300048828125,201.8800048828125\n", - "2024-03-01,502.29998779296875,202.63999938964844\n", + "...\n", + "2024-04-10,519.8300170898438,171.75999450683594\n", "\"\"\"\n", "\n", - "# Read the data into a pandas DataFrame\n", + "# Load the data into a pandas DataFrame\n", "df = pd.read_csv(StringIO(data))\n", "\n", - "# Calculate the percentage change for META and TSLA\n", - "meta_start = df['META'].iloc[0]\n", - "meta_end = df['META'].iloc[-1]\n", - "meta_change = ((meta_end - meta_start) / meta_start) * 100\n", + "# Calculate the mean, max, min, and standard deviation for each stock\n", + "meta_mean = df['META'].mean()\n", + "meta_max = df['META'].max()\n", + "meta_min = df['META'].min()\n", + "meta_std = df['META'].std()\n", "\n", - "tsla_start = df['TSLA'].iloc[0]\n", - "tsla_end = df['TSLA'].iloc[-1]\n", - "tsla_change = ((tsla_end - tsla_start) / tsla_start) * 100\n", + "tesla_mean = df['TESLA'].mean()\n", + "tesla_max = df['TESLA'].max()\n", + "tesla_min = df['TESLA'].min()\n", + "tesla_std = df['TESLA'].std()\n", "\n", "# Print the results\n", - "print(f\"META stock changed by {meta_change:.2f}% from the beginning to the end of the period.\")\n", - "print(f\"TSLA stock changed by {tsla_change:.2f}% from the beginning to the end of the period.\")\n", + "print(f\"META - Mean: {meta_mean}, Max: {meta_max}, Min: {meta_min}, Std: {meta_std}\")\n", + "print(f\"TESLA - Mean: {tesla_mean}, Max: {tesla_max}, Min: {tesla_min}, Std: {tesla_std}\")\n", "```\n", "\n", - "Please run this script to calculate the percentage change for both META and TSLA stocks. Once we have the results, we can proceed to write the blog post.\n", + "After running this code, we can use the results to write the blog post. Here is a possible blog post based on the results:\n", + "\n", + "---\n", + "\n", + "Title: A Comparative Analysis of META and TESLA Stocks in Early 2024\n", + "\n", + "In the first quarter of 2024, the stock market saw some interesting movements in the tech sector. Two companies that stood out during this period were META and TESLA. \n", + "\n", + "META, the social media giant, had an average stock price of [META Mean] during this period. The highest it reached was [META Max], while the lowest was [META Min]. The standard deviation, a measure of how spread out the prices were, was [META Std].\n", + "\n", + "On the other hand, TESLA, the electric vehicle and clean energy company, had an average stock price of [TESLA Mean]. The stock reached a high of [TESLA Max] and a low of [TESLA Min]. The standard deviation for TESLA was [TESLA Std].\n", + "\n", + "These figures show that both META and TESLA had their ups and downs during this period. However, the higher standard deviation for [Company with higher Std] indicates that its stock price fluctuated more compared to [Company with lower Std].\n", + "\n", + "As we move further into 2024, it will be interesting to see how these trends evolve. Will META and TESLA continue on their current trajectories, or will we see a shift in the market dynamics? Only time will tell.\n", + "\n", + "---\n", + "\n", + "Please replace [META Mean], [META Max], [META Min], [META Std], [TESLA Mean], [TESLA Max], [TESLA Min], [TESLA Std], [Company with higher Std], and [Company with lower Std] with the actual values obtained from the Python code.\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[31m\n", - ">>>>>>>> EXECUTING CODE BLOCK 0 (inferred language is python)...\u001b[0m\n", + ">>>>>>>> EXECUTING CODE BLOCK (inferred language is python)...\u001b[0m\n", "\u001b[33muser_proxy\u001b[0m (to assistant):\n", "\n", "exitcode: 0 (execution succeeded)\n", - "Code output: \n", - "META stock changed by 45.05% from the beginning to the end of the period.\n", - "TSLA stock changed by -18.43% from the beginning to the end of the period.\n", + "Code output: META - Mean: 403.53000895182294, Max: 519.8300170898438, Min: 344.4700012207031, Std: 100.72287240911488\n", + "TESLA - Mean: 219.54332987467447, Max: 248.4199981689453, Min: 171.75999450683594, Std: 41.68075797345465\n", "\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33massistant\u001b[0m (to user_proxy):\n", "\n", - "Based on the analysis, we can see that META stock experienced a significant increase of 45.05% from the beginning to the end of the period, while TSLA stock saw a decrease of 18.43% in the same timeframe. This information will be the foundation for our blog post.\n", - "\n", - "Here's a draft for the blog post:\n", + "Great! Now that we have the results, let's write the blog post.\n", "\n", "---\n", "\n", - "**A Tale of Two Stocks: META Soars as TSLA Dips in Early 2024**\n", - "\n", - "As we navigate through the early months of 2024, the stock market has presented a mixed bag of performances, with some companies experiencing remarkable growth and others facing downward trends. Two notable examples that have caught the attention of investors are META and TSLA.\n", + "Title: A Comparative Analysis of META and TESLA Stocks in Early 2024\n", "\n", - "META, the social media giant, has seen its stock price skyrocket by an impressive 45.05% since the start of the year. This surge can be attributed to a series of successful product launches and strategic acquisitions, positioning the company at the forefront of innovation and profitability. Investors have shown their confidence in META's future, as the company continues to diversify its revenue streams and expand its global reach.\n", + "In the first quarter of 2024, the stock market saw some interesting movements in the tech sector. Two companies that stood out during this period were META and TESLA. \n", "\n", - "On the other hand, TSLA, the electric vehicle pioneer, has faced a challenging period with its stock price declining by 18.43%. The dip reflects concerns over production delays, increased competition in the electric vehicle market, and a series of high-profile controversies surrounding its CEO. Despite these setbacks, loyal supporters of TSLA believe in the company's long-term vision and its potential to rebound as it addresses these challenges.\n", + "META, the social media giant, had an average stock price of 403.53 during this period. The highest it reached was 519.83, while the lowest was 344.47. The standard deviation, a measure of how spread out the prices were, was 100.72.\n", "\n", - "The contrasting trajectories of META and TSLA highlight the volatile nature of the stock market, where fortunes can shift rapidly based on consumer sentiment, market conditions, and internal company developments. As investors continue to monitor these stocks, the coming months will be crucial in determining whether META can maintain its upward momentum and if TSLA can steer back onto the path of growth.\n", + "On the other hand, TESLA, the electric vehicle and clean energy company, had an average stock price of 219.54. The stock reached a high of 248.42 and a low of 171.76. The standard deviation for TESLA was 41.68.\n", "\n", - "For those looking to invest, the current landscape serves as a reminder of the importance of due diligence and a balanced portfolio. While the allure of quick gains is tempting, the market's unpredictability necessitates a strategy that can withstand the ebbs and flows of stock valuations.\n", + "These figures show that both META and TESLA had their ups and downs during this period. However, the higher standard deviation for META indicates that its stock price fluctuated more compared to TESLA.\n", "\n", - "As we move forward into 2024, all eyes will be on these two industry titans, as their stories unfold and continue to shape the investment world.\n", + "As we move further into 2024, it will be interesting to see how these trends evolve. Will META and TESLA continue on their current trajectories, or will we see a shift in the market dynamics? Only time will tell.\n", "\n", "---\n", "\n", - "This blog post provides a narrative based on the data analysis we performed. It should engage readers by discussing the recent performance of both stocks and offering a broader perspective on market volatility.\n", + "This blog post provides a brief but engaging analysis of the META and TESLA stocks based on the provided data. It highlights the mean, max, min, and standard deviation of the stock prices for both companies, and provides some commentary on what these figures might mean for the future.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33muser_proxy\u001b[0m (to assistant):\n", + "\n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33massistant\u001b[0m (to user_proxy):\n", "\n", "TERMINATE\n", "\n", @@ -692,11 +769,14 @@ " file_content = \"No data found.\"\n", " return \"Analyze the data and write a brief but engaging blog post. \\n Data: \\n\" + file_content\n", "\n", + "\n", "# followup of the previous question\n", "chat_res = user_proxy.initiate_chat(\n", " recipient=assistant,\n", " message=my_message_generator,\n", " file_name=\"coding/stock_price_ytd.csv\",\n", + " summary_method=\"reflection_with_llm\",\n", + " summary_args={\"summary_prompt\": \"Return the blog post in Markdown format.\"},\n", ")" ] }, @@ -709,38 +789,28 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Based on the analysis, we can see that META stock experienced a significant increase of 45.05% from the beginning to the end of the period, while TSLA stock saw a decrease of 18.43% in the same timeframe. This information will be the foundation for our blog post.\n", - "\n", - "Here's a draft for the blog post:\n", - "\n", "---\n", "\n", - "**A Tale of Two Stocks: META Soars as TSLA Dips in Early 2024**\n", - "\n", - "As we navigate through the early months of 2024, the stock market has presented a mixed bag of performances, with some companies experiencing remarkable growth and others facing downward trends. Two notable examples that have caught the attention of investors are META and TSLA.\n", + "# A Comparative Analysis of META and TESLA Stocks in Early 2024\n", "\n", - "META, the social media giant, has seen its stock price skyrocket by an impressive 45.05% since the start of the year. This surge can be attributed to a series of successful product launches and strategic acquisitions, positioning the company at the forefront of innovation and profitability. Investors have shown their confidence in META's future, as the company continues to diversify its revenue streams and expand its global reach.\n", + "In the first quarter of 2024, the stock market saw some interesting movements in the tech sector. Two companies that stood out during this period were META and TESLA. \n", "\n", - "On the other hand, TSLA, the electric vehicle pioneer, has faced a challenging period with its stock price declining by 18.43%. The dip reflects concerns over production delays, increased competition in the electric vehicle market, and a series of high-profile controversies surrounding its CEO. Despite these setbacks, loyal supporters of TSLA believe in the company's long-term vision and its potential to rebound as it addresses these challenges.\n", + "META, the social media giant, had an average stock price of 403.53 during this period. The highest it reached was 519.83, while the lowest was 344.47. The standard deviation, a measure of how spread out the prices were, was 100.72.\n", "\n", - "The contrasting trajectories of META and TSLA highlight the volatile nature of the stock market, where fortunes can shift rapidly based on consumer sentiment, market conditions, and internal company developments. As investors continue to monitor these stocks, the coming months will be crucial in determining whether META can maintain its upward momentum and if TSLA can steer back onto the path of growth.\n", + "On the other hand, TESLA, the electric vehicle and clean energy company, had an average stock price of 219.54. The stock reached a high of 248.42 and a low of 171.76. The standard deviation for TESLA was 41.68.\n", "\n", - "For those looking to invest, the current landscape serves as a reminder of the importance of due diligence and a balanced portfolio. While the allure of quick gains is tempting, the market's unpredictability necessitates a strategy that can withstand the ebbs and flows of stock valuations.\n", + "These figures show that both META and TESLA had their ups and downs during this period. However, the higher standard deviation for META indicates that its stock price fluctuated more compared to TESLA.\n", "\n", - "As we move forward into 2024, all eyes will be on these two industry titans, as their stories unfold and continue to shape the investment world.\n", + "As we move further into 2024, it will be interesting to see how these trends evolve. Will META and TESLA continue on their current trajectories, or will we see a shift in the market dynamics? Only time will tell.\n", "\n", - "---\n", - "\n", - "This blog post provides a narrative based on the data analysis we performed. It should engage readers by discussing the recent performance of both stocks and offering a broader perspective on market volatility.\n", - "\n", - "\n" + "---\n" ] } ], @@ -752,281 +822,43 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Let's check how much the above chat cost" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "({'total_cost': 0.7510199999999999, 'gpt-4': {'cost': 0.7510199999999999, 'prompt_tokens': 14984, 'completion_tokens': 5025, 'total_tokens': 20009}}, {'total_cost': 0.3678, 'gpt-4': {'cost': 0.3678, 'prompt_tokens': 7478, 'completion_tokens': 2391, 'total_tokens': 9869}})\n" - ] - } - ], - "source": [ - "print(chat_res.cost)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Use a Different Code Execution Environment\n", + "This is the blog post that the agents generated.\n", "\n", - "The code execution happened in a separate process, so the plot is not directly displayed in the notebook. Is it possible to change the code execution environment into IPython?\n", + "# A Comparative Analysis of META and TESLA Stocks in Early 2024\n", "\n", - "Yes! In the following we demonstrate how to extend the `UserProxyAgent` to use a different code execution environment." - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [], - "source": [ - "class IPythonUserProxyAgent(autogen.UserProxyAgent):\n", - " def __init__(self, name: str, **kwargs):\n", - " super().__init__(name, **kwargs)\n", - " self._ipython = get_ipython()\n", + "In the first quarter of 2024, the stock market saw some interesting movements in the tech sector. Two companies that stood out during this period were META and TESLA. \n", "\n", - " def run_code(self, code, **kwargs):\n", - " result = self._ipython.run_cell(\"%%capture --no-display cap\\n\" + code)\n", - " log = self._ipython.ev(\"cap.stdout\")\n", - " log += self._ipython.ev(\"cap.stderr\")\n", - " if result.result is not None:\n", - " log += str(result.result)\n", - " exitcode = 0 if result.success else 1\n", - " if result.error_before_exec is not None:\n", - " log += f\"\\n{result.error_before_exec}\"\n", - " exitcode = 1\n", - " if result.error_in_exec is not None:\n", - " log += f\"\\n{result.error_in_exec}\"\n", - " exitcode = 1\n", - " return exitcode, log, None" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The implementation overrides two functions in `UserProxyAgent`:\n", - "* constructor. We get the ipython instance as the code execution environment.\n", - "* `run_code`. We execute the code with the ipython instance.\n", + "META, the social media giant, had an average stock price of 403.53 during this period. The highest it reached was 519.83, while the lowest was 344.47. The standard deviation, a measure of how spread out the prices were, was 100.72.\n", "\n", - "In addition, we create a **user defined message function** shown below to generate the initiate message to the chat. In this function, we append the raw message, carryover information (both of which are provide via `context`), and a string specifing IPython execution together as the final message." - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "metadata": {}, - "outputs": [], - "source": [ - "def my_ipy_message_generator(sender, recipient, context):\n", - " raw_message = context.get(\"raw_message\", \"\")\n", - " carryover = context.get(\"carryover\", \"\")\n", - " return raw_message + carryover + \"If you suggest code, the code will be executed in IPython.\"" + "On the other hand, TESLA, the electric vehicle and clean energy company, had an average stock price of 219.54. The stock reached a high of 248.42 and a low of 171.76. The standard deviation for TESLA was 41.68.\n", + "\n", + "These figures show that both META and TESLA had their ups and downs during this period. However, the higher standard deviation for META indicates that its stock price fluctuated more compared to TESLA.\n", + "\n", + "As we move further into 2024, it will be interesting to see how these trends evolve. Will META and TESLA continue on their current trajectories, or will we see a shift in the market dynamics? Only time will tell." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "With the new `IPythonUserProxyAgent`, we are able to run the code within the current notebook environment and display the plot directly." + "Let's check how much the above chat cost" ] }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "\u001b[33mipython_user_proxy\u001b[0m (to assistant):\n", - "\n", - "Plot a chart of META and TESLA stock price gain YTD. Use data from the following csv file if it exists: coding/stock_price_ytd.csv. Use csv to read the file. Otherwise, figure out how to get the data.If you suggest code, the code will be executed in IPython.\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33massistant\u001b[0m (to ipython_user_proxy):\n", - "\n", - "First, we need to check if the file `coding/stock_price_ytd.csv` exists and if it contains the necessary data for META and TESLA stock price gains YTD (Year-To-Date). We will attempt to read the file using Python's `csv` module. If the file does not exist or we cannot obtain the necessary data from it, we will then look for an alternative way to get the data.\n", - "\n", - "Let's start by checking if the file exists and reading its contents.\n", - "\n", - "```python\n", - "# filename: check_csv_file.py\n", - "\n", - "import csv\n", - "import os\n", - "\n", - "# Define the path to the CSV file\n", - "file_path = 'coding/stock_price_ytd.csv'\n", - "\n", - "# Check if the file exists\n", - "if os.path.exists(file_path):\n", - " try:\n", - " # Attempt to read the file and print its contents\n", - " with open(file_path, mode='r') as file:\n", - " csv_reader = csv.DictReader(file)\n", - " meta_data = []\n", - " tesla_data = []\n", - " for row in csv_reader:\n", - " if 'META' in row:\n", - " meta_data.append(row)\n", - " if 'TESLA' in row:\n", - " tesla_data.append(row)\n", - " print(\"META data:\", meta_data)\n", - " print(\"TESLA data:\", tesla_data)\n", - " except Exception as e:\n", - " print(f\"An error occurred while reading the file: {e}\")\n", - "else:\n", - " print(\"The file does not exist.\")\n", - "```\n", - "\n", - "Please execute the above code to check for the file and read its contents. If the file exists and contains the required data, we will proceed to plot the chart. If not, we will explore alternative ways to obtain the data.\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[31m\n", - ">>>>>>>> EXECUTING CODE BLOCK 0 (inferred language is python)...\u001b[0m\n", - "\u001b[33mipython_user_proxy\u001b[0m (to assistant):\n", - "\n", - "exitcode: 0 (execution succeeded)\n", - "Code output: \n", - "META data: [{'Date': '2024-01-02', 'META': '346.2900085449219', 'TSLA': '248.4199981689453'}, {'Date': '2024-01-03', 'META': '344.4700012207031', 'TSLA': '238.4499969482422'}, {'Date': '2024-01-04', 'META': '347.1199951171875', 'TSLA': '237.92999267578125'}, {'Date': '2024-01-05', 'META': '351.95001220703125', 'TSLA': '237.49000549316406'}, {'Date': '2024-01-08', 'META': '358.6600036621094', 'TSLA': '240.4499969482422'}, {'Date': '2024-01-09', 'META': '357.42999267578125', 'TSLA': '234.9600067138672'}, {'Date': '2024-01-10', 'META': '370.4700012207031', 'TSLA': '233.94000244140625'}, {'Date': '2024-01-11', 'META': '369.6700134277344', 'TSLA': '227.22000122070312'}, {'Date': '2024-01-12', 'META': '374.489990234375', 'TSLA': '218.88999938964844'}, {'Date': '2024-01-16', 'META': '367.4599914550781', 'TSLA': '219.91000366210938'}, {'Date': '2024-01-17', 'META': '368.3699951171875', 'TSLA': '215.5500030517578'}, {'Date': '2024-01-18', 'META': '376.1300048828125', 'TSLA': '211.8800048828125'}, {'Date': '2024-01-19', 'META': '383.45001220703125', 'TSLA': '212.19000244140625'}, {'Date': '2024-01-22', 'META': '381.7799987792969', 'TSLA': '208.8000030517578'}, {'Date': '2024-01-23', 'META': '385.20001220703125', 'TSLA': '209.13999938964844'}, {'Date': '2024-01-24', 'META': '390.70001220703125', 'TSLA': '207.8300018310547'}, {'Date': '2024-01-25', 'META': '393.17999267578125', 'TSLA': '182.6300048828125'}, {'Date': '2024-01-26', 'META': '394.1400146484375', 'TSLA': '183.25'}, {'Date': '2024-01-29', 'META': '401.0199890136719', 'TSLA': '190.92999267578125'}, {'Date': '2024-01-30', 'META': '400.05999755859375', 'TSLA': '191.58999633789062'}, {'Date': '2024-01-31', 'META': '390.1400146484375', 'TSLA': '187.2899932861328'}, {'Date': '2024-02-01', 'META': '394.7799987792969', 'TSLA': '188.86000061035156'}, {'Date': '2024-02-02', 'META': '474.989990234375', 'TSLA': '187.91000366210938'}, {'Date': '2024-02-05', 'META': '459.4100036621094', 'TSLA': '181.05999755859375'}, {'Date': '2024-02-06', 'META': '454.7200012207031', 'TSLA': '185.10000610351562'}, {'Date': '2024-02-07', 'META': '469.5899963378906', 'TSLA': '187.5800018310547'}, {'Date': '2024-02-08', 'META': '470.0', 'TSLA': '189.55999755859375'}, {'Date': '2024-02-09', 'META': '468.1099853515625', 'TSLA': '193.57000732421875'}, {'Date': '2024-02-12', 'META': '468.8999938964844', 'TSLA': '188.1300048828125'}, {'Date': '2024-02-13', 'META': '460.1199951171875', 'TSLA': '184.02000427246094'}, {'Date': '2024-02-14', 'META': '473.2799987792969', 'TSLA': '188.7100067138672'}, {'Date': '2024-02-15', 'META': '484.0299987792969', 'TSLA': '200.4499969482422'}, {'Date': '2024-02-16', 'META': '473.32000732421875', 'TSLA': '199.9499969482422'}, {'Date': '2024-02-20', 'META': '471.75', 'TSLA': '193.75999450683594'}, {'Date': '2024-02-21', 'META': '468.0299987792969', 'TSLA': '194.77000427246094'}, {'Date': '2024-02-22', 'META': '486.1300048828125', 'TSLA': '197.41000366210938'}, {'Date': '2024-02-23', 'META': '484.0299987792969', 'TSLA': '191.97000122070312'}, {'Date': '2024-02-26', 'META': '481.739990234375', 'TSLA': '199.39999389648438'}, {'Date': '2024-02-27', 'META': '487.04998779296875', 'TSLA': '199.72999572753906'}, {'Date': '2024-02-28', 'META': '484.0199890136719', 'TSLA': '202.0399932861328'}, {'Date': '2024-02-29', 'META': '490.1300048828125', 'TSLA': '201.8800048828125'}, {'Date': '2024-03-01', 'META': '502.29998779296875', 'TSLA': '202.63999938964844'}]\n", - "TESLA data: []\n", - "\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33massistant\u001b[0m (to ipython_user_proxy):\n", - "\n", - "The output indicates that the file `coding/stock_price_ytd.csv` exists and contains the necessary data for META stock price gains YTD. However, it seems that the TESLA data is not separated correctly, as the TESLA data appears to be within the same rows as the META data, but the script is looking for a separate 'TESLA' entry in the rows.\n", - "\n", - "We need to adjust the script to correctly parse the TESLA data from the same rows as the META data. Let's modify the script to extract both META and TESLA stock prices from the same rows and then plot the chart.\n", - "\n", - "```python\n", - "# filename: plot_stock_prices.py\n", - "\n", - "import csv\n", - "import os\n", - "import matplotlib.pyplot as plt\n", - "\n", - "# Define the path to the CSV file\n", - "file_path = 'coding/stock_price_ytd.csv'\n", - "\n", - "# Initialize lists to hold the parsed data\n", - "dates = []\n", - "meta_prices = []\n", - "tesla_prices = []\n", - "\n", - "# Check if the file exists\n", - "if os.path.exists(file_path):\n", - " try:\n", - " # Read the file and parse the data\n", - " with open(file_path, mode='r') as file:\n", - " csv_reader = csv.DictReader(file)\n", - " for row in csv_reader:\n", - " dates.append(row['Date'])\n", - " meta_prices.append(float(row['META']))\n", - " tesla_prices.append(float(row['TSLA']))\n", - " \n", - " # Plot the data\n", - " plt.figure(figsize=(14, 7))\n", - " plt.plot(dates, meta_prices, label='META', marker='o')\n", - " plt.plot(dates, tesla_prices, label='TESLA', marker='x')\n", - " \n", - " # Formatting the plot\n", - " plt.title('META vs TESLA Stock Price Gain YTD')\n", - " plt.xlabel('Date')\n", - " plt.ylabel('Stock Price')\n", - " plt.xticks(rotation=45)\n", - " plt.legend()\n", - " plt.tight_layout()\n", - " \n", - " # Show the plot\n", - " plt.show()\n", - " except Exception as e:\n", - " print(f\"An error occurred while processing the file: {e}\")\n", - "else:\n", - " print(\"The file does not exist.\")\n", - "```\n", - "\n", - "Please execute the above code to plot the chart of META and TESLA stock price gains YTD.\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[31m\n", - ">>>>>>>> EXECUTING CODE BLOCK 0 (inferred language is python)...\u001b[0m\n" - ] - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAABW0AAAKyCAYAAACuWPzHAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8g+/7EAAAACXBIWXMAAA9hAAAPYQGoP6dpAAD4XElEQVR4nOzdd3hUZfrG8Xsy6W0gPUAIIdRQpBcbvSgiq1hQse3qKrJrXde6ApbVdVdX14L+XDsgNiywgoIFFVB6DVJDS4WETHqbOb8/QkbGJGQCSWaSfD/XNReZ97TnDAnizXue12QYhiEAAAAAAAAAgEfwcncBAAAAAAAAAIBfEdoCAAAAAAAAgAchtAUAAAAAAAAAD0JoCwAAAAAAAAAehNAWAAAAAAAAADwIoS0AAAAAAAAAeBBCWwAAAAAAAADwIIS2AAAAAAAAAOBBCG0BAAAAAAAAwIMQ2gIAAABucsMNNyg4ONht1x85cqRGjhzptus3tNmzZ8tkMrm7DAAAgDNGaAsAAFq0t956SyaTSSaTST/++GO17YZhKC4uTiaTSRdddJHTtqrjanrdeuut+u677065z8mvk11xxRUymUy67777GvXeXTVy5EiX7mH27NmSpE6dOtW6z8SJE53O/eOPP+qCCy5Q+/bt5e/vr44dO2ry5MlasGCB034mk0l/+tOfXK755Zdflslk0tChQ+t1r3a7Xe+8846GDh2qsLAwhYSEqFu3brruuuv0008/OfZLTk7W7NmzdeDAgXqdvylVBZRVr8DAQCUlJenhhx9WXl6eu8ur09atW3XjjTcqISFB/v7+Cg4OVr9+/fTXv/5V+/fvd3d5euqpp2QymfTll1/WuP3CCy+UxWLR+PHjXfr5ueGGGyQ5/7x5eXkpNDRU3bt317XXXqvly5c34R0CAABP5u3uAgAAAJqCv7+/FixYoHPPPddpfOXKlTpy5Ij8/PxqPG7cuHG67rrrqo1369ZN8fHxevfdd53GH3jgAQUHB+uhhx6q8Xx5eXlavHixOnXqpPfee88RDLnTQw89pJtuusnxft26dfrPf/6jBx98UD179nSM9+3b1/F1v379dM8991Q7V7t27Rxff/jhh7ryyivVr18/3XHHHWrbtq1SUlL0/fff67XXXtPVV1992jXPnz9fnTp10tq1a7V371516dLFpeNuv/12vfTSS5oyZYquueYaeXt7a9euXVq6dKk6d+6sYcOGSaoMbefMmaORI0eqU6dOp11nU5g7d66Cg4NVUFCgr776Sk888YS++eYbrVq1qs7vra+++qqJqnT22muvacaMGYqIiNA111yjHj16qKKiQtu3b9c777yj5557TsXFxTKbzfU678MPP6z777+/QWq85557tGDBAt12223avn27AgICHNs+/PBDLV26VC+99JL69+/v9GdESkqKHnnkEf3xj3/Ueeed5xhPTEx0fN2hQwc9+eSTkqTCwkLt3btXixYt0rx583TFFVdo3rx58vHxaZD7AAAAzZQBAADQgr355puGJOPSSy81IiIijPLycqftN998szFw4EAjPj7emDRpktM2ScbMmTPrdb1evXoZI0aMqHX7G2+8Yfj4+BjffPONIcn47rvv6nX+pvDhhx8akoxvv/22xu01fVY1SUpKMnr16mWUlpZW25aZmen0vj6f9f79+w1JxqJFi4zIyEhj9uzZLh2XkZFhmEwm4+abb662zW63O9VU12fQUK6//nojKCjotI6dNWuWIck4evSo0/ill15qSDJWr15d67GFhYWndc2GsGrVKsNsNhvnn3++kZeXV217cXGx8fDDDxsVFRVuqM7ZmjVrDC8vL+OBBx5wjOXl5Rnt2rUzhg0bZthstmrHrFu3zpBkvPnmmzWec8SIEUavXr2qjVdUVBi33XabIcn461//2mD3AAAAmifaIwAAgFbhqquuUnZ2ttPjx2VlZfroo4/OaMZnfc2fP1/jxo3TqFGj1LNnT82fP7/OY8rLyxUWFqYbb7yx2ra8vDz5+/vrL3/5i2PshRdeUK9evRQYGKi2bdtq0KBB1doRNIV9+/Zp8ODB8vX1rbYtKirqtM87f/58tW3bVpMmTdJll13m0mcoVc6ANAxD55xzTrVtJpPJUdNbb72lyy+/XJI0atQox6Ps3333nWP/l19+Wb169ZKfn5/atWunmTNnKjc3t9p5f/75Z1144YVq27atgoKC1LdvXz3//POnrHPz5s2KjIzUyJEjVVBQ4NK9nWz06NGO+5UqH8fv3bu3NmzYoPPPP1+BgYF68MEHHdt+29O2pKREs2fPVrdu3eTv76/Y2Fhdeuml2rdvn2Mfu92u5557Tr169ZK/v7+io6N1yy236Pjx43XWN2fOHJlMJs2fP18hISHVtvv7++uxxx5zmmX7ww8/6PLLL1fHjh3l5+enuLg43XXXXSouLnY6tqaetlWtNz799FP17t1bfn5+6tWrl5YtW1ZnrcOGDdOtt96qf/3rX0pOTpZUOZs3KytL//d//ycvr4b73ymz2az//Oc/SkpK0osvviir1dpg5wYAAM0PoS0AAGgVOnXqpOHDh+u9995zjC1dulRWq1XTpk2r9biSkhIdO3as2qusrKzeNaSlpenbb7/VVVddJakySP7oo4/qPJePj48uueQSffrpp9X2/fTTT1VaWuq4h9dee0233367kpKS9Nxzz2nOnDnq16+ffv7553rXeyrl5eU1fi4nh2jx8fH6+uuvdeTIkQa99vz583XppZfK19dXV111lfbs2aN169bVeVx8fLykykfbi4qKat3v/PPP1+233y5JevDBB/Xuu+/q3XffdbSKmD17tmbOnKl27drpmWee0dSpU/Xqq69q/PjxKi8vd5xn+fLlOv/885WcnKw77rhDzzzzjEaNGqUlS5bUeu1169Zp9OjR6t+/v5YuXXpai5RVhavh4eGOsezsbF1wwQXq16+fnnvuOY0aNarGY202my666CLNmTNHAwcO1DPPPKM77rhDVqtV27dvd+x3yy236N5779U555yj559/XjfeeKPmz5+vCRMmOH0Gv1VUVKRvvvlGI0eOVIcOHVy+p6rfsxkzZuiFF17QhAkT9MILL9TYuqQmP/74o2677TZNmzZNTz/9tEpKSjR16lRlZ2fXeeyTTz6pyMhI3XLLLdqwYYNeeukl/eUvf1GfPn1crt9VZrNZV111lYqKimrswQ0AAFoRd0/1BQAAaExV7RHWrVtnvPjii0ZISIhRVFRkGIZhXH755caoUaMMw6j5kX9Jtb7ee++9Gq93qvYI//rXv4yAgADHI+G7d+82JBmffPJJnffx5ZdfGpKMxYsXO41feOGFRufOnR3vp0yZUuOj1/XhSnuE2j6XJ5980rHf66+/bkgyfH19jVGjRhl/+9vfjB9++KHGR8rlYnuE9evXG5KM5cuXG4ZR2dagQ4cOxh133OHSvV133XWGJKNt27bGJZdcYvzrX/8ydu7c6fJnkJWVZfj6+hrjx493uo8XX3zRkGS88cYbhmFUPuqekJBgxMfHG8ePH3c6h91ud3x9cnuEH3/80QgNDTUmTZpklJSU1HkvVe0Rdu3aZRw9etRISUkxXn31VcPPz8+Ijo52tEAYMWKEIcl45ZVXqp1jxIgRTt+vb7zxhiHJePbZZ6vtW1X3Dz/8YEgy5s+f77R92bJlNY6fbMuWLYYk484776y2LTs72zh69KjjdXJbjaqf2ZM9+eSThslkMg4ePFjtMzlZ1ffg3r17q9Xxwgsv1FrryT766CNDkhEWFmZ07ty5xnqqnG57hCqffPKJIcl4/vnnXaoNAAC0TMy0BQAArcYVV1yh4uJiLVmyRPn5+VqyZEmdrRGmTJmi5cuXV3vVNlPxVObPn69JkyY5Hgnv2rWrBg4c6NLj/aNHj1ZERITef/99x9jx48e1fPlyXXnllY6xNm3a6MiRIy7NPD0TQ4cOrfFzqZpFLEm///3vtWzZMo0cOVI//vijHnvsMZ133nnq2rWrVq9efVrXnT9/vqKjox2fv8lk0pVXXqmFCxfKZrPVefybb76pF198UQkJCfrkk0/0l7/8RT179tSYMWOUmppa5/ErVqxQWVmZ7rzzTqdH42+++WaFhobqf//7nyRp06ZNSklJ0Z133qk2bdo4naOmxcG+/fZbTZgwQWPGjNGiRYtqXRivJt27d1dkZKQSEhJ0yy23qEuXLvrf//6nwMBAxz5+fn41ttf4rY8//lgRERH685//XG1bVd0ffvihLBaLxo0b5zTLeuDAgQoODta3335b6/nz8vIkqcYZxJ07d1ZkZKTj9fnnnzu2nbwIWGFhoY4dO6azzz5bhmFo06ZNdd7X2LFjnRYC69u3r0JDQ7V///46j5WkqVOn6sILL1ROTo5eeuklp3oaWtVnk5+f32jXAAAAns/b3QUAAAA0lcjISI0dO1YLFixQUVGRbDabLrvsslMe06FDB40dO/aMr71z505t2rRJ1113nfbu3esYHzlypF566SXl5eUpNDS01uO9vb01depULViwQKWlpfLz89OiRYtUXl7uFNred999WrFihYYMGaIuXbpo/Pjxuvrqq2vs43omIiIiXPpcJkyYoAkTJqioqEgbNmzQ+++/r1deeUUXXXSRfvnll3r1trXZbFq4cKFGjRrl6NcqVQbIzzzzjL7++muNHz/+lOfw8vLSzJkzNXPmTGVnZ2vVqlV65ZVXtHTpUk2bNk0//PDDKY8/ePCgpMqg9GS+vr7q3LmzY3tVi4LevXvXeV8lJSWaNGmSBg4cqA8++EDe3vX7K/rHH3+s0NBQ+fj4qEOHDk7hZJX27dvX2Fv4t/bt26fu3bufsoY9e/bIarXW+nuXlZVV67FV/2BRU6/ezz77TOXl5dqyZYtTj2ZJOnTokB555BF9/vnn1frmutL7tWPHjtXG2rZt61IP3iqDBw/WF198oUGDBrl8zOmo+mxq6vcLAABaD0JbAADQqlx99dW6+eablZGRoQsuuKDaLMjGMm/ePEnSXXfdpbvuuqva9o8//rjOmZDTpk3Tq6++qqVLl+p3v/udPvjgA/Xo0UNnnXWWY5+ePXtq165dWrJkiZYtW6aPP/5YL7/8sh555BHNmTOnYW+qHgIDA3XeeefpvPPOU0REhObMmaOlS5fq+uuvd/kc33zzjdLT07Vw4UItXLiw2vb58+fXGdqeLDw8XBdffLEuvvhijRw5UitXrtTBgwcdvW+bip+fny688EJ99tlnWrZsmS666KJ6HX/++ecrIiLilPs05MxQu92uqKioWmeIR0ZG1npsly5d5O3t7dQft8qIESMkqVpgbLPZNG7cOOXk5Oi+++5Tjx49FBQUpNTUVN1www2y2+111nzyomYnMwyjzmObWtVn06VLFzdXAgAA3InQFgAAtCqXXHKJbrnlFv30009OrQYak2EYWrBggUaNGqXbbrut2vbHHntM8+fPrzO0Pf/88xUbG6v3339f5557rr755hs99NBD1fYLCgrSlVdeqSuvvFJlZWW69NJL9cQTT+iBBx6Qv79/g93X6aqaqZienl6v4+bPn6+oqCi99NJL1bYtWrRIn3zyiV555ZXTCigHDRqklStXKj09XfHx8TW2MJB+Xcxs165d6ty5s2O8rKxMKSkpjtnHVbNdt2/fXueMZJPJpPnz52vKlCm6/PLLtXTpUo0cObLe99AQEhMT9fPPP6u8vFw+Pj617rNixQqdc8459f6sg4KCHAF5amqq2rdvX+cx27Zt0+7du/X22287LTy2fPnyel27ObDZbFqwYIECAwN17rnnurscAADgRvS0BQAArUpwcLDmzp2r2bNna/LkyU1yzVWrVunAgQO68cYbddlll1V7XXnllfr222+VlpZ2yvN4eXnpsssu0+LFi/Xuu++qoqLCqTWCJGVnZzu99/X1VVJSkgzDUHl5eYPf26l8/fXXNY5/8cUXkqq3GDiV4uJiLVq0SBdddFGNn+Gf/vQn5efnO/VB/a2MjAwlJydXGy8rK9PXX38tLy8vx+zGoKAgSVJubq7TvmPHjpWvr6/+85//OM3SfP3112W1WjVp0iRJ0oABA5SQkKDnnnuu2jlqmt3p6+urRYsWafDgwZo8ebLWrl3r0ufS0KZOnapjx47pxRdfrLatqu4rrrhCNptNjz32WLV9Kioqqt3vbz3yyCOy2WyaPn16jW0Sfvv5VM2SPXncMAw9//zzdd5Pc2Kz2XT77bdr586duv3220/ZLgUAALR8zLQFAACtTn0eyd+9e7ejtcHJoqOjNW7cOJfOMX/+fJnNZkeg91sXX3yxHnroIS1cuFB33333Kc915ZVX6oUXXtCsWbPUp08f9ezZ02n7+PHjFRMTo3POOUfR0dHauXOnXnzxRacF0BpCampqjZ9LcHCwfve730mqXMQtISFBkydPVmJiogoLC7VixQotXrzYEU6ebP369Xr88cernXPkyJFKTU1Vfn6+Lr744hrrGTZsmCIjIzV//vxqQXaVI0eOaMiQIRo9erTGjBmjmJgYZWVl6b333tOWLVt05513OtoM9OvXT2azWf/4xz9ktVrl5+en0aNHKyoqSg888IDmzJmjiRMn6uKLL9auXbv08ssva/DgwZo+fbqkyoB97ty5mjx5svr166cbb7xRsbGx+uWXX7Rjxw59+eWX1eoLCAjQkiVLNHr0aF1wwQVauXKlSz1xG9J1112nd955R3fffbfWrl2r8847z/H7dtttt2nKlCkaMWKEbrnlFj355JPavHmzxo8fLx8fH+3Zs0cffvihnn/++VP2ij7vvPP04osv6s9//rO6du2qa665Rj169FBZWZl2796t+fPny9fXVzExMZKkHj16KDExUX/5y1+Umpqq0NBQffzxx/XqR+tprFar4+enqKhIe/fu1aJFi7Rv3z5NmzatxkAcAAC0MgYAAEAL9uabbxqSjHXr1p1yv/j4eGPSpElOY5JqfY0YMaLG8/Tq1ctpW1lZmREeHm6cd955p7x+QkKC0b9//zrvx263G3FxcYYk4/HHH6+2/dVXXzXOP/98Izw83PDz8zMSExONe++917BarXWeu8qHH35oSDK+/fbbGrfHx8fX+rnEx8c79nvvvfeMadOmGYmJiUZAQIDh7+9vJCUlGQ899JCRl5fndM5TfdaPPfaYMXnyZMPf398o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z9a///1vBQUFNWC1AAAAAAAAaCzZBaWaszhZn29JkyQlRATpqUv7aGjncDdX1jIR2tZXeZH093and+z3/6x81fa+Lg+mSb4NG3SeffbZ8vLychorKKhc5e/ZZ5/VZZddppiYGPXq1Utnn322pkyZogsuuKBe13j99dc1bdo0mc1m9e7dW507d9aHH36oG264oaFuAwAAAAAAAGfAZje0NiVHWfkligrx15CEMJm9TDIMQ4u3pmv25zuUU1gmL5N08/mdddfYbvL3Mbu77BaL0LaVe//999WzZ88atyUlJWn79u3asGGDVq1ape+//16TJ0/WDTfcoP/+978unT83N1eLFi3Sjz/+6BibPn26Xn/9dUJbAAAAAAAAD7Bse7rmLE5WurXEMRZr8dftY7rq651ZWrEzU5LUIyZET1/WV307tHFTpa0HoW19+QRWznitr6qWCGbfyjYJ599b2SqhvtduYHFxcerSpUut2728vDR48GANHjxYd955p+bNm6drr71WDz30kBISEuo8/4IFC1RSUuLUw9YwDNntdu3evVvdunVrkPsAAAAAAABA/S3bnq4Z8zbK+M14urVEDyzaJknyMZv0p1FdNWNkony9vaqfBA2O0La+TKb6tyhY+XRlYDvqocrFyKoWITP7Oi9O1gwkJSVJkgoLXevD+/rrr+uee+6pNqv2tttu0xtvvKGnnnqqoUsEAAAAAACAC2x2Q3MWJ1cLbE/mYzbps5nnKqldaJPVBULbxlcV0FYFttKvv377hPN7N8jOzlZGRobTWJs2beTv76/LLrtM55xzjs4++2zFxMQoJSVFDzzwgLp166YePXo49rdardq8ebPTOcLDw5Wdna2NGzdq/vz5TvtL0lVXXaVHH31Ujz/+uLy9+TYEAAAAAABoamtTcpxaItSk3GbIWlzeRBWhCmlZY7PbnAPbKlXv7bamr+kkY8eOrTb23nvvadq0aZowYYLee+89Pfnkk7JarYqJidHo0aM1e/Zsp6D1u+++U//+/Z3O8Yc//EEBAQFKSkqqFthK0iWXXKI//elP+uKLL3TxxRc3/I0BAAAAAAA0gNoW6GruDMPQlsO5Lu2blX/qYBcNz2QYxqlmQLcKeXl5slgsslqtCg39dap3SUmJUlJSlJCQIH9/fzdW2LrwuQMAAAAAAE9Q2wJdsyYnaWLvWDdWdnoMw1By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3r3bWrZsaQ8//LBt2bLFNm7caMOHD7cuXbrYHXfcEdQsv2g51v/yyy9Wq1Yte/TRR23v3r22fv1683g89sUXX4Qs041jvRvHebPoOda7sb+EAkVbFNvhw4dtxIgRNnz4cMvIyAgUt/7xj39Yq1at7MiRI0HfKQ4dOmS333673XHHHZaVlRU4+V599dX25JNP2h/+8IeQvvn517/+ZRMnTrQFCxbYxRdfbP3797f58+fbxIkTbcOGDUHLOXDggPXt29cSExMD86666irr2LGjeTweO//88+2FF14IWp7f4sWLrUWLFrZq1arAvFtvvdWuvPJKGzp0qN1+++2B1zmYb3xmz55t7du3ty1btgTmrVy50s444wyrUaNGvg/0wbJv3z5r166dffTRR4F5WVlZtnDhQmvTpo117do1MD8Y27HP57PXXnvNBgwYYAsWLLARI0ZY165d8xVug/1mMiMjw0aNGmXXX3+9HTx4MPDaff7551avXj3btWtXUPPyGjFiRKD4361bNzv//POtV69eduaZZ9qZZ55p69atM7PgrPOWLVusYcOG+VpdzZkzx+rXr29vvPGGmYWmBd+6deusZcuWNm/evMC8kSNHWvfu3e2iiy6ySy+91DZt2mRmwS3UpKSkWJs2bWz16tWBedu3b7c2bdpY3bp1rWPHjkHLMuNYXxaP9WZm11xzjT3//PNmZta9e3c777zzrFmzZtasWTPr3Llz4MN1sF5bN47zZmabNm2y5s2b2w8//BCYd/DgQfvvf/9rrVq1squuuiqoednZ2fbcc8/ZZZddZkuWLLGLL77YOnbsGNLCbUZGho0fP96uvvpqW758ubVq1cratWuX78NJsDMPHDhg1113XeCLT//j/+EPf7ALLrjAzIJ/Ttu7d6916dLFGjduHJh3/fXXW+fOnS0uLs4uv/zyfOf0YBowYIBNmTLFzMx69eplvXr1smrVqlnbtm3tiiuuCHreypUrzev12tixYwPz/vGPf1h8fLwtW7bMzELzBcCsWbOsdevW+fbT3/72t3bPPffYyJEjbcKECUHP/Oyzz6xLly62d+/ewDrNnTvXatasaU2bNg168YtzGue0YElJSbGmTZsG9kkz5zj1pz/9yTp06GAPPPBA0LLMoudYn5aWZkOHDrW7777bfD5f4PGvuOIK+/vf/24vv/yyzZo1K6iZfqV5rHfrOG8WHcd6N/aXUKFPWxSbmSkrK0vnnHOO4uPjFRMTI0mqXbu29u7dq4yMjKBnxsTE6IorrtBdd92l2NhYeb1ePfXUU/rPf/6j1atX69tvv9Wzzz6rBx54IOjZkpSYmKhp06apc+fOGj16tBITE3XllVdq3LhxKleunKTgDDgSGxur2267TXXr1tVll12mAQMGKDs7W48++qh+/PFHnXHGGZoyZUrQOyuvWLGiMjIy9O677yozM1NPPvmk/u///k+tWrVSvXr1NGfOHJ177rmSgttnZ0ZGhvbv369t27YF5h09elQ1a9bUY489pg0bNuj9998PWp4kJSUlyePx6Ntvvw3Mi42NVadOnfTaa6/pwIEDGj16tKTgDKDg8Xh0+eWX684771Tnzp3117/+VZ07d9aHH36oV155RYcOHQp6f6Rmpvj4ePXp00cVK1YM7KPVq1fX4cOHQ7KP5rV48WL961//UvXq1TVt2jT973//008//aQGDRpo0KBBkoKzHcXFxenVV1/Vs88+G5jXq1cvXXrppXr66aeVnp4eWPdgSkpKUnZ2tl555RWlpqbq8ccf1z//+U9ddtlluvTSS7V//37169dPGRkZQR+E48CBA0pJSQlcT0tLk9fr1QsvvKD9+/fney5KimN92TvWS86gdYsXL9Zrr72mpKQkffzxx/r55581ZcoUZWRk6IorrpAUvAFk3DjOS85r6fP59OOPPwbmVaxYUQMHDtS4ceO0fv16vfrqq0HLi4mJUZ8+fXTjjTeqffv2+vzzz1W7dm1NnDhR06ZNCxwPgnms93q96tatm4YMGaLWrVsrOTlZWVlZuvXWW7Vo0SL5fL6gH4N8Pp/S09PVtWtXeb3ewOM3atRIW7duVVZWVlDzJGc9r7jiClWvXl133XWX+vbtq/3792vEiBGaNm2a9u3bp+eff14zZswIenZ2draSk5M1ceJEVapUSR9++KFSUlL06KOPasOGDRo5cmRQ82JiYvTiiy9qwoQJgW1l+PDh6tq1qyZMmKDMzMyQDO4UGxurw4cP64svvpAkTZw4Ue+99568Xq92796tDz74QNdcc01QM9PS0rRv3z4dPXo0sE45OTlq3ry5hgwZop9++klz5swJWp7P5+OcxjktKCpVqqSsrKzAGApmpqpVq+qOO+7Q4MGDNXv2bH3++edBy/N6verevXtUHOsvvvhi3XHHHfJ4PPJ6vXr66ac1bdo0TZs2Ta+88ooefPBBvfDCC0HPLs1jvVvHeSk6jvVuvDcKGZeKxSgj/D/DNcttyTZv3jw7++yz833rtmLFiqBlHjlyJPB3SkqKVapUKV//nGPHjrVOnTrZjh07gpbp/2Zx9erV1q1bt8D8/v37W4UKFax79+42c+bMoOWZOS0B/vvf/1rTpk2tZ8+e+Z7r/fv323nnnWfXXnttUDP3799vo0ePtoYNG1q/fv0sLi7O/vvf/wZunzVrltWpUyfoPxH59ddfrUmTJnb99dfblClTbObMmZaUlBT45rFnz55B7RfL/63ak08+aT179rRPP/003+3Z2dn20EMPWd++fQMto0IhKyurwBa3b775Zokf27+Ou3fvDszz75ObNm2yFi1a2J49ewK3/fTTTyXOzJs7e/ZsO+ecc6xnz56Bn7z48+fPn28NGjSwn3/+ucR5BX277583a9Ysa9q0qf373/8+6X1LIisry/72t79Zo0aNbMCAAVahQgV7//33A7dv3LjRqlatmm9eMOzZs8cuvPBCu+KKK2zSpEn26aefWpUqVezBBx80M7Nrr73Wbr755qBmRtOx3szK9LHev4++88471q9fP+vfv789+uij+e7z4YcfWqtWrQIt4oNh06ZNduaZZ5bacd7v8OHDdvPNN1v//v1tyZIl+W47cuSIXXnllTZkyJCgZh7/E+CMjIx8LW79t3/yySdBy8y7v5iZHT16NF+rEjPntQ/m9rt58+bA3/7jwscff2ydO3fOd7+8LXlKas+ePfanP/3JzjjjDLvgggvy7f87d+60s846y+69996g5fmPCePHj7fLLrvMrrjiinz9/GdmZtqzzz5rPXv2tH379gUt92TGjx9vLVu2DDynwT6v7dixw6655hpr3Lix9e/f32JiYvIdd9966y0766yzLCUlJWiZK1eutAoVKtj9999vP/zwg82bN88SExMDLb2aNGlizzzzTFCy/Me/vNtNqM9p/sy87ydL65zmVxrnNP96Hj161D766KNSO6f5X6/SPqeZOb/au+CCC2zQoEEn/HotLS3NOnToYCNGjAhq5tGjR0+4XhaP9YcPHw78/dNPP1nlypVt6tSplp2dbVlZWTZ06FDr37//Cc9HcfnX68knnyyVY/2pWniG+jhv5u6x/r777gv5sd7PjfdGoUDRFkWya9cuW7Roka1cuTJfwSfvweSnn36yhg0b2sGDB83MeRPSr1+/Yh/g8mbu3bvXzJydy3+w2759e75leOONN6x169YlOqDmzcz7OD6fz84//3zbuHGj3XDDDVavXj177bXXbNCgQdalS5cSFTMLem4PHDhgn3/+uX355ZeBk4n/8v7777fzzjuvRAfygjL3799vGzZssB9++MHOPvvsfJ2wL1q0yJo1a2bz588vdubxuf6i4vz5861Dhw525plnWsOGDfMNInXdddcF+qkqrrwfov3bzsaNG61Xr1528cUX27fffpvv/u+99541a9YsX9GzJJnH87+OmZmZgcLtM888Y3fccYfFxMTYxo0bS5SZ9+dE/ut+a9eutTp16tjWrVvNzGzcuHHWrl27Yne4X9B67tmzx2655RaLi4uzvn375rtt2bJl1qpVqxK9ESjsz9z8r28wFLQNZWdn2759+2zFihXWunXrwOvm8/lszZo11rp16xO2reLm5n1Nf/nlFxs8eLC1bNnSWrRoke8Dyr333lvivjrT0tJs/fr1tnXr1sCx3Cy0x/q8mXn7eg7lsT5vZt4PCD6fzy644IKQHOsLWs+DBw+G9Fhf0Hr++uuv1qdPn8CAInnNnj3bWrRoUaKfzebN9A8K4z/ON23aNCTHeTPng8isWbNsxowZgW1m2bJlVrduXRs0aFC+bkXMzF588UXr0KFDvu28JJn+gkveY4SZ80HBX7j94IMP7M4777S6desGjsNFtW/fPlu7dq2lpqbm62LH5/MFBn88cuRI4MPJ3LlzbcSIEda+fftiH+v9mTt37sz3fOXtfmbq1KnWokWLwPUxY8bYddddF1jG4mampqYGMlNTU+3111+3r7766oTn+frrr7eBAwcWK6ugTP9yr1ixItAv3vFfLnz66afWsmVL27lzZ9By/fupf73867l//36rXbu2jRo1qkRZBWX6n99t27bZokWL7OOPP7YOHTrkG2xoxowZduaZZ9rKlSuDkuk/Nnz11VdWs2bNQHcpeZ/jiy++uMSFeP8+cfx7o1Ce0woahDWvUJzTTpUZqnNa3kz/dnrkyJGQntMKWs/Nmzdb3759Q3ZOM3O6udi1a5cdPHgwsAzz5s2z+Ph4u+eee04YAG3s2LHWp0+f024Lhc08vquvUB3rC1pPs/z7S7CP9adaz6NHj9qvv/5qZrnb0bPPPmsdOnTI994tGJkrV64M6bE+b6b/+Tz+p/rBPs4fn+v/XLFjx46QHusLyvz666+tVq1aITvWu/HeqDRQtEWhLV261Jo3b25Nmza1Bg0aWOfOnW3u3Lkn3O+HH36wKlWq2OHDh+3xxx+32NjYYhf5CpN5/En/vvvusyFDhhT7IH6qzMzMTDv//POtTp061rBhQ1u8eLGZOW82hw4dGjihBCNzzpw5ZuZ8M1/QyX7o0KF2zz33FLsvluMzO3XqFMg0czru7ty5c75vnh577DFr3759iVoBFJTr72tw165dtnnz5nwniKysLLvkkkvsqaeeMrPi9T2zcuVKu+6662zhwoWBx/CfnFesWBEYkMbf/2lmZqY98MADdv755xf7g/zxmQXxL4O/xW1CQoIlJiYWeyThwmSaOQWMChUq2M6dO238+PEWHx9f7H20oEz/Prl582YbMmSIlStXzm677Tbz+Xy2e/due/LJJ61Dhw7FftNTlOf2m2++sbp16+b79jgYmXmLiWbOm4TOnTvnG/DiiSeesFatWpWo5UFBuf7jQXp6uqWnp+c77vh8Phs8eHCJWiympKRYr169rEWLFnbmmWfa/fffX+BrFcxjfWEyj9/3S3qsP1VmRkZGSI71BWX6P7iH6lhfUKa/5dOaNWusS5cuVrVq1cCAIkeOHLHHH3/cevbsGfiiNBiZ/uJkampqSI7zZs5IyGeffba1bt3aGjdubL/5zW8CuQsXLrTKlSvbVVddZd98803gf+644w4bOHBgsX9VcXzmJZdccsK2639dMzIy7JJLLrG4uDirWLHiaY/VJ7N06VJr166dnXXWWXbmmWfakCFD7JdffjGz3OOv/wPS0aNHrW3bthYbG2vly5cPaaaZ03q4QYMGZmb26KOPWkxMTL5+v0uauXz5cjNzjn/Hv2Y5OTl2xRVX2Lhx44qVd7JMf9+CS5cuterVq1uDBg3yHevHjBljffv2LXDE+pLkHv/8+i8fffRR69q1a6DP9GBlDh48OPD8mjnFqO7du9v69esD88aMGWPdu3cv9hfaBWX6n99ff/3VkpOTA8dbM+d41KdPH3v55ZfNrHjHhtWrV9vo0aNP+MLmeME8pxUmM9jntJNl+ny+kH1+KSgzb8viUJzTCsr0v+dbu3ZtSM5pZs575379+lmbNm2sTZs29uKLLwYeb+rUqZaQkGDDhw/PNy7I9ddfb9ddd12xx1U4PvOll16y9PT0fPcJ9rG+MJlmwT3WFzYzrzvuuMNuueWWYg9ud6rXc/ny5SE51hdmPYN9nC8o98UXX8z35dD8+fODfqw/VeaWLVssJSUl6Md6N94blRaKtiiU7du3W6NGjeyRRx6xVatW2ccff2xDhw61uLi4wE9//TvXzz//bF26dLGHHnrIEhISAk3PQ5GZ16FDh2zs2LFWs2bNfB3CByvTP0DA+++/bz169DhhvYr7zeKpMv0doR+fM3bsWKtTp06xv/0qzHO7c+dO69q1q1144YU2ePBgu+WWW6xatWr5DrDByo2NjbV33333hPtv2bLFxo4dazVq1Djtm+2TWbdunTVs2NCqVKliV111VaAgmrf4tWrVKhs8eLC1aNHC6tevb3369LGqVasWe11PllkQ/0nkrrvusqpVqxZ72y1K5oYNG6xjx442fPjwEu2jp3pu/W9Qt27dar///e+tQYMGlpiYaJ06dbI6deoUuzBdlPU0c1oMNWzY0B5++OFit+ooTGZ6eroNHTrUevToYb169bJrr73WatSoUaL95VTPb0Hrsnr1ahszZoxVrVo18CalqH755RerWbOmPfTQQ/bDDz/YU089ZV27dg10k5L3TVSwjvVFyTQLzrH+dJlmZh988IH17NkzaMf6k2X+5z//KfD+wTjWFyZz3bp1ds0111ijRo2sVq1adt5551n16tWLvY8WdT2DcZw3c76Aq1Gjhv3+97+3DRs22JQpU6xZs2b53ogvWLDAOnbsaJ06dbI2bdrY5ZdfbomJiSd0m1DSzLzPnX9f9R8TR44cadWqVSv2trt582arU6eOPfjgg/bTTz/ZSy+9ZAMGDLAqVaoEvmDO+4WgmTMwZPXq1fMV5IKd6f8gNHXqVDv33HPt8ccft4SEhGJ/EDpZZlJSkv34449mlr9YnJ2dbePGjbP69esXezs6Veb3339vZs5I3126dLEWLVpYs2bN7NJLL7UqVaqU6FhflNfUzPmA7fF47F//+lfQM5OSkgKZ69evtxo1athll11m9957r40YMcKqVq1a7P3lVJl5Bwr027Nnj40dO9Zq165d7J+1r1271mrVqmWJiYn2wAMP2Nq1a09632Cd04qSaRacc9qpMv3n0GCf04q7niU5p50sM+/7zmCf08yc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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[33mipython_user_proxy\u001b[0m (to assistant):\n", - "\n", - "exitcode: 0 (execution succeeded)\n", - "Code output: \n", - "\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33massistant\u001b[0m (to ipython_user_proxy):\n", - "\n", - "It appears that the code executed successfully and the chart should have been displayed on your screen. Since I cannot view the chart, I will assume that you were able to see the META and TESLA stock price gains YTD plotted correctly.\n", - "\n", - "If you have any further requests or need assistance with another task, feel free to ask. Otherwise, if everything is done, please let me know.\n", - "\n", - "TERMINATE\n", - "\n", - "--------------------------------------------------------------------------------\n" + "({'total_cost': 0.84249, 'gpt-4-0613': {'cost': 0.84249, 'prompt_tokens': 22513, 'completion_tokens': 2785, 'total_tokens': 25298}}, {'total_cost': 0.84249, 'gpt-4-0613': {'cost': 0.84249, 'prompt_tokens': 22513, 'completion_tokens': 2785, 'total_tokens': 25298}})\n" ] - }, - { - "data": { - "text/plain": [ - "ChatResult(chat_id=None, chat_history=[{'content': 'Plot a chart of META and TESLA stock price gain YTD. Use data from the following csv file if it exists: coding/stock_price_ytd.csv. Use csv to read the file. Otherwise, figure out how to get the data.If you suggest code, the code will be executed in IPython.', 'role': 'assistant'}, {'content': 'First, we need to check if the file `coding/stock_price_ytd.csv` exists and if it contains the necessary data for META and TESLA stock price gains YTD (Year-To-Date). We will attempt to read the file using Python\\'s `csv` module. If the file does not exist or we cannot obtain the necessary data from it, we will then look for an alternative way to get the data.\\n\\nLet\\'s start by checking if the file exists and reading its contents.\\n\\n```python\\n# filename: check_csv_file.py\\n\\nimport csv\\nimport os\\n\\n# Define the path to the CSV file\\nfile_path = \\'coding/stock_price_ytd.csv\\'\\n\\n# Check if the file exists\\nif os.path.exists(file_path):\\n try:\\n # Attempt to read the file and print its contents\\n with open(file_path, mode=\\'r\\') as file:\\n csv_reader = csv.DictReader(file)\\n meta_data = []\\n tesla_data = []\\n for row in csv_reader:\\n if \\'META\\' in row:\\n meta_data.append(row)\\n if \\'TESLA\\' in row:\\n tesla_data.append(row)\\n print(\"META data:\", meta_data)\\n print(\"TESLA data:\", tesla_data)\\n except Exception as e:\\n print(f\"An error occurred while reading the file: {e}\")\\nelse:\\n print(\"The file does not exist.\")\\n```\\n\\nPlease execute the above code to check for the file and read its contents. If the file exists and contains the required data, we will proceed to plot the chart. If not, we will explore alternative ways to obtain the data.', 'role': 'user'}, {'content': \"exitcode: 0 (execution succeeded)\\nCode output: \\nMETA data: [{'Date': '2024-01-02', 'META': '346.2900085449219', 'TSLA': '248.4199981689453'}, {'Date': '2024-01-03', 'META': '344.4700012207031', 'TSLA': '238.4499969482422'}, {'Date': '2024-01-04', 'META': '347.1199951171875', 'TSLA': '237.92999267578125'}, {'Date': '2024-01-05', 'META': '351.95001220703125', 'TSLA': '237.49000549316406'}, {'Date': '2024-01-08', 'META': '358.6600036621094', 'TSLA': '240.4499969482422'}, {'Date': '2024-01-09', 'META': '357.42999267578125', 'TSLA': '234.9600067138672'}, {'Date': '2024-01-10', 'META': '370.4700012207031', 'TSLA': '233.94000244140625'}, {'Date': '2024-01-11', 'META': '369.6700134277344', 'TSLA': '227.22000122070312'}, {'Date': '2024-01-12', 'META': '374.489990234375', 'TSLA': '218.88999938964844'}, {'Date': '2024-01-16', 'META': '367.4599914550781', 'TSLA': '219.91000366210938'}, {'Date': '2024-01-17', 'META': '368.3699951171875', 'TSLA': '215.5500030517578'}, {'Date': '2024-01-18', 'META': '376.1300048828125', 'TSLA': '211.8800048828125'}, {'Date': '2024-01-19', 'META': '383.45001220703125', 'TSLA': '212.19000244140625'}, {'Date': '2024-01-22', 'META': '381.7799987792969', 'TSLA': '208.8000030517578'}, {'Date': '2024-01-23', 'META': '385.20001220703125', 'TSLA': '209.13999938964844'}, {'Date': '2024-01-24', 'META': '390.70001220703125', 'TSLA': '207.8300018310547'}, {'Date': '2024-01-25', 'META': '393.17999267578125', 'TSLA': '182.6300048828125'}, {'Date': '2024-01-26', 'META': '394.1400146484375', 'TSLA': '183.25'}, {'Date': '2024-01-29', 'META': '401.0199890136719', 'TSLA': '190.92999267578125'}, {'Date': '2024-01-30', 'META': '400.05999755859375', 'TSLA': '191.58999633789062'}, {'Date': '2024-01-31', 'META': '390.1400146484375', 'TSLA': '187.2899932861328'}, {'Date': '2024-02-01', 'META': '394.7799987792969', 'TSLA': '188.86000061035156'}, {'Date': '2024-02-02', 'META': '474.989990234375', 'TSLA': '187.91000366210938'}, {'Date': '2024-02-05', 'META': '459.4100036621094', 'TSLA': '181.05999755859375'}, {'Date': '2024-02-06', 'META': '454.7200012207031', 'TSLA': '185.10000610351562'}, {'Date': '2024-02-07', 'META': '469.5899963378906', 'TSLA': '187.5800018310547'}, {'Date': '2024-02-08', 'META': '470.0', 'TSLA': '189.55999755859375'}, {'Date': '2024-02-09', 'META': '468.1099853515625', 'TSLA': '193.57000732421875'}, {'Date': '2024-02-12', 'META': '468.8999938964844', 'TSLA': '188.1300048828125'}, {'Date': '2024-02-13', 'META': '460.1199951171875', 'TSLA': '184.02000427246094'}, {'Date': '2024-02-14', 'META': '473.2799987792969', 'TSLA': '188.7100067138672'}, {'Date': '2024-02-15', 'META': '484.0299987792969', 'TSLA': '200.4499969482422'}, {'Date': '2024-02-16', 'META': '473.32000732421875', 'TSLA': '199.9499969482422'}, {'Date': '2024-02-20', 'META': '471.75', 'TSLA': '193.75999450683594'}, {'Date': '2024-02-21', 'META': '468.0299987792969', 'TSLA': '194.77000427246094'}, {'Date': '2024-02-22', 'META': '486.1300048828125', 'TSLA': '197.41000366210938'}, {'Date': '2024-02-23', 'META': '484.0299987792969', 'TSLA': '191.97000122070312'}, {'Date': '2024-02-26', 'META': '481.739990234375', 'TSLA': '199.39999389648438'}, {'Date': '2024-02-27', 'META': '487.04998779296875', 'TSLA': '199.72999572753906'}, {'Date': '2024-02-28', 'META': '484.0199890136719', 'TSLA': '202.0399932861328'}, {'Date': '2024-02-29', 'META': '490.1300048828125', 'TSLA': '201.8800048828125'}, {'Date': '2024-03-01', 'META': '502.29998779296875', 'TSLA': '202.63999938964844'}]\\nTESLA data: []\\n\", 'role': 'assistant'}, {'content': 'The output indicates that the file `coding/stock_price_ytd.csv` exists and contains the necessary data for META stock price gains YTD. However, it seems that the TESLA data is not separated correctly, as the TESLA data appears to be within the same rows as the META data, but the script is looking for a separate \\'TESLA\\' entry in the rows.\\n\\nWe need to adjust the script to correctly parse the TESLA data from the same rows as the META data. Let\\'s modify the script to extract both META and TESLA stock prices from the same rows and then plot the chart.\\n\\n```python\\n# filename: plot_stock_prices.py\\n\\nimport csv\\nimport os\\nimport matplotlib.pyplot as plt\\n\\n# Define the path to the CSV file\\nfile_path = \\'coding/stock_price_ytd.csv\\'\\n\\n# Initialize lists to hold the parsed data\\ndates = []\\nmeta_prices = []\\ntesla_prices = []\\n\\n# Check if the file exists\\nif os.path.exists(file_path):\\n try:\\n # Read the file and parse the data\\n with open(file_path, mode=\\'r\\') as file:\\n csv_reader = csv.DictReader(file)\\n for row in csv_reader:\\n dates.append(row[\\'Date\\'])\\n meta_prices.append(float(row[\\'META\\']))\\n tesla_prices.append(float(row[\\'TSLA\\']))\\n \\n # Plot the data\\n plt.figure(figsize=(14, 7))\\n plt.plot(dates, meta_prices, label=\\'META\\', marker=\\'o\\')\\n plt.plot(dates, tesla_prices, label=\\'TESLA\\', marker=\\'x\\')\\n \\n # Formatting the plot\\n plt.title(\\'META vs TESLA Stock Price Gain YTD\\')\\n plt.xlabel(\\'Date\\')\\n plt.ylabel(\\'Stock Price\\')\\n plt.xticks(rotation=45)\\n plt.legend()\\n plt.tight_layout()\\n \\n # Show the plot\\n plt.show()\\n except Exception as e:\\n print(f\"An error occurred while processing the file: {e}\")\\nelse:\\n print(\"The file does not exist.\")\\n```\\n\\nPlease execute the above code to plot the chart of META and TESLA stock price gains YTD.', 'role': 'user'}, {'content': 'exitcode: 0 (execution succeeded)\\nCode output: \\n', 'role': 'assistant'}, {'content': 'It appears that the code executed successfully and the chart should have been displayed on your screen. Since I cannot view the chart, I will assume that you were able to see the META and TESLA stock price gains YTD plotted correctly.\\n\\nIf you have any further requests or need assistance with another task, feel free to ask. Otherwise, if everything is done, please let me know.\\n\\nTERMINATE', 'role': 'user'}], summary='It appears that the code executed successfully and the chart should have been displayed on your screen. Since I cannot view the chart, I will assume that you were able to see the META and TESLA stock price gains YTD plotted correctly.\\n\\nIf you have any further requests or need assistance with another task, feel free to ask. Otherwise, if everything is done, please let me know.\\n\\n', cost=({'total_cost': 2.1070799999999994, 'gpt-4': {'cost': 2.1070799999999994, 'prompt_tokens': 45338, 'completion_tokens': 12449, 'total_tokens': 57787}}, {'total_cost': 1.7238599999999995, 'gpt-4': {'cost': 1.7238599999999995, 'prompt_tokens': 37832, 'completion_tokens': 9815, 'total_tokens': 47647}}), human_input=[])" - ] - }, - "execution_count": 32, - "metadata": {}, - "output_type": "execute_result" } ], "source": [ - "ipy_user = IPythonUserProxyAgent(\n", - " \"ipython_user_proxy\",\n", - " human_input_mode=\"NEVER\",\n", - " max_consecutive_auto_reply=10,\n", - " is_termination_msg=lambda x: x.get(\"content\", \"\").rstrip().endswith(\"TERMINATE\")\n", - " or x.get(\"content\", \"\").rstrip().endswith('\"TERMINATE\".'),\n", - " code_execution_config={\n", - " \"use_docker\": False, # Please set use_docker=True if docker is available to run the generated code. Using docker is safer than running the generated code directly.\n", - " },\n", - ")\n", - "# the assistant receives a message from the user, which contains the task description\n", - "ipy_user.initiate_chat(\n", - " assistant,\n", - " message=my_ipy_message_generator,\n", - " raw_message=\"\"\"Plot a chart of META and TESLA stock price gain YTD. \"\"\",\n", - " carryover=\"Use data from the following csv file if it exists: coding/stock_price_ytd.csv. Use csv to read the file. Otherwise, figure out how to get the data.\",\n", - ")" + "print(chat_res.cost)" ] } ], @@ -1053,7 +885,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.13" + "version": "3.10.14" }, "vscode": { "interpreter": { diff --git a/notebook/agentchat_azr_ai_search.ipynb b/notebook/agentchat_azr_ai_search.ipynb new file mode 100644 index 00000000000..f4521f60d27 --- /dev/null +++ b/notebook/agentchat_azr_ai_search.ipynb @@ -0,0 +1,413 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Assistants with Azure Cognitive Search and Azure Identity\n", + "\n", + "This notebook demonstrates the use of Assistant Agents in conjunction with Azure Cognitive Search and Azure Identity. Assistant Agents use tools that interact with Azure Cognitive Search to extract pertinent data.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Prerequisites\n", + "\n", + "Before running this notebook, please ensure the following prerequisites are met:\n", + " \n", + "\n", + "### Dependencies\n", + "1. **Autogen**\n", + "2. **Azure SDK**\n", + "3. **Cognitive Search**/**AI Search**\n", + "\n", + "If you have AI search enabled in your Azure Portal, you can use the following code to create an assistant agent that can search Azure Cognitive Search.\n", + "\n", + "**AI search setup details:**\n", + "- Documentation: \n", + " - Create search service: https://learn.microsoft.com/en-us/azure/search/search-create-service-portal \n", + " - Search index: https://learn.microsoft.com/en-us/azure/search/search-how-to-create-search-index?tabs=portal \n", + " hybrid search: https://learn.microsoft.com/en-us/azure/search/hybrid-search-how-to-query\n", + "\n", + "- Youtube walkthrough: https://www.youtube.com/watch?v=6Zfuw-UJZ7k\n", + "\n", + "\n", + "### Install Azure CLI\n", + "This notebook requires the Azure CLI for authentication purposes. Follow these steps to install and configure it:\n", + "\n", + "1. **Download and Install Azure CLI**:\n", + " - Visit the [Azure CLI installation page](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) and follow the instructions for your operating system.\n", + " - Mac users can install Azure CLI using Homebrew with the command `brew install azure-cli` \n", + "\n", + "2. **Verify Installation**:\n", + " - In the below cell execute `az --version` to check if Azure CLI is installed correctly.\n", + "\n", + "4. **Login to Azure**:\n", + " - In the below cell execute `az login` to log into your Azure account. This step is necessary as the notebook uses `AzureCliCredential` which retrieves the token based on the Azure account currently logged in.\n", + "\n", + "### Check Azure CLI Installation\n", + "Run the cell below to check if Azure CLI is installed and properly configured on your system." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Check Azure CLI Installation and Login Status" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Check Azure CLI installation and login status\n", + "# !az --version\n", + "# !az login" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Install required packages\n", + "Run the cell below to install the required packages for this notebook.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!pip3 install pyautogen==0.2.16\n", + "!pip3 install python-dotenv==1.0.1\n", + "!pip3 install pyautogen[graph]>=0.2.11\n", + "!pip3 install azure-search-documents==11.4.0b8\n", + "!pip3 install azure-identity==1.12.0" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next you will import the required packages for this notebook.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "import os\n", + "\n", + "import requests\n", + "from azure.identity import DefaultAzureCredential\n", + "from azure.search.documents import SearchClient\n", + "from dotenv import load_dotenv\n", + "\n", + "import autogen\n", + "from autogen import AssistantAgent, UserProxyAgent, register_function\n", + "from autogen.cache import Cache\n", + "\n", + "load_dotenv()\n", + "\n", + "# Import Cognitive Search index ENV\n", + "AZURE_SEARCH_SERVICE = os.getenv(\"AZURE_SEARCH_SERVICE\")\n", + "AZURE_SEARCH_INDEX = os.getenv(\"AZURE_SEARCH_INDEX\")\n", + "AZURE_SEARCH_KEY = os.getenv(\"AZURE_SEARCH_KEY\")\n", + "AZURE_SEARCH_API_VERSION = os.getenv(\"AZURE_SEARCH_API_VERSION\")\n", + "AZURE_SEARCH_SEMANTIC_SEARCH_CONFIG = os.getenv(\"AZURE_SEARCH_SEMANTIC_SEARCH_CONFIG\")\n", + "AZURE_SEARCH_SERVICE_ENDPOINT = os.getenv(\"AZURE_SEARCH_SERVICE_ENDPOINT\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, you need to authenticate and create a `SearchClient` instance." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "credential = DefaultAzureCredential()\n", + "endpoint = AZURE_SEARCH_SERVICE_ENDPOINT\n", + "\n", + "from azure.identity import AzureCliCredential\n", + "\n", + "credential = AzureCliCredential()\n", + "token = credential.get_token(\"https://cognitiveservices.azure.com/.default\")\n", + "\n", + "print(\"TOKEN\", token.token)\n", + "\n", + "client = SearchClient(endpoint=endpoint, index_name=\"test-index\", credential=credential)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Then, load the configuration list and define the configuration for the `AssistantAgent`." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "config_list = autogen.config_list_from_json(\n", + " env_or_file=\"OAI_CONFIG_LIST\",\n", + ")\n", + "\n", + "gpt4_config = {\n", + " \"cache_seed\": 42,\n", + " \"temperature\": 0,\n", + " \"config_list\": config_list,\n", + " \"timeout\": 120,\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Define your tool function `search` that will interact with the Azure Cognitive Search service." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "def search(query: str):\n", + " payload = json.dumps(\n", + " {\n", + " \"search\": query,\n", + " \"vectorQueries\": [{\"kind\": \"text\", \"text\": query, \"k\": 5, \"fields\": \"vector\"}],\n", + " \"queryType\": \"semantic\",\n", + " \"semanticConfiguration\": AZURE_SEARCH_SEMANTIC_SEARCH_CONFIG,\n", + " \"captions\": \"extractive\",\n", + " \"answers\": \"extractive|count-3\",\n", + " \"queryLanguage\": \"en-US\",\n", + " }\n", + " )\n", + "\n", + " response = list(client.search(payload))\n", + "\n", + " output = []\n", + " for result in response:\n", + " result.pop(\"titleVector\")\n", + " result.pop(\"contentVector\")\n", + " output.append(result)\n", + "\n", + " return output" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Define the `AssistantAgent` and `UserProxyAgent` instances, and register the `search` function to them." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cog_search = AssistantAgent(\n", + " name=\"COGSearch\",\n", + " system_message=\"You are a helpful AI assistant. \"\n", + " \"You can help with Azure Cognitive Search.\"\n", + " \"Return 'TERMINATE' when the task is done.\",\n", + " llm_config=gpt4_config,\n", + ")\n", + "\n", + "user_proxy = UserProxyAgent(\n", + " name=\"User\",\n", + " llm_config=False,\n", + " is_termination_msg=lambda msg: msg.get(\"content\") is not None and \"TERMINATE\" in msg[\"content\"],\n", + " human_input_mode=\"NEVER\",\n", + ")\n", + "\n", + "register_function(\n", + " search,\n", + " caller=cog_search,\n", + " executor=user_proxy,\n", + " name=\"search\",\n", + " description=\"A tool for searching the Cognitive Search index\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, initiate a chat." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mUser\u001b[0m (to COGSearch):\n", + "\n", + "Search for 'What is Azure?' in the 'test-index' index\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mCOGSearch\u001b[0m (to User):\n", + "\n", + "\u001b[32m***** Suggested tool Call (call_6Db6DFPNEp7J7Dz5dkAbbjDY): search *****\u001b[0m\n", + "Arguments: \n", + "{\"query\":\"What is Azure?\"}\n", + "\u001b[32m***********************************************************************\u001b[0m\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[35m\n", + ">>>>>>>> EXECUTING ASYNC FUNCTION search...\u001b[0m\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mUser\u001b[0m (to COGSearch):\n", + "\n", + "\u001b[33mUser\u001b[0m (to COGSearch):\n", + "\n", + "\u001b[32m***** Response from calling tool \"call_6Db6DFPNEp7J7Dz5dkAbbjDY\" *****\u001b[0m\n", + "[{\"id\": \"40\", \"title\": \"Azure Cognitive Search\", \"category\": \"AI + Machine Learning\", \"content\": \"Azure Cognitive Search is a fully managed search-as-a-service that enables you to build rich search experiences for your applications. It provides features like full-text search, faceted navigation, and filters. Azure Cognitive Search supports various data sources, such as Azure SQL Database, Azure Blob Storage, and Azure Cosmos DB. You can use Azure Cognitive Search to index your data, create custom scoring profiles, and integrate with other Azure services. It also integrates with other Azure services, such as Azure Cognitive Services and Azure Machine Learning.\", \"@search.score\": 9.1308, \"@search.reranker_score\": null, \"@search.highlights\": null, \"@search.captions\": null}, {\"id\": \"90\", \"title\": \"Azure Cognitive Services\", \"category\": \"AI + Machine Learning\", \"content\": \"Azure Cognitive Services is a collection of AI services and APIs that enable you to build intelligent applications using pre-built models and algorithms. It provides features like computer vision, speech recognition, and natural language processing. Cognitive Services supports various platforms, such as .NET, Java, Node.js, and Python. You can use Azure Cognitive Services to build chatbots, analyze images and videos, and process and understand text. It also integrates with other Azure services, such as Azure Machine Learning and Azure Cognitive Search.\", \"@search.score\": 5.9858904, \"@search.reranker_score\": null, \"@search.highlights\": null, \"@search.captions\": null}, {\"id\": \"68\", \"title\": \"Azure Database for MariaDB\", \"category\": \"Databases\", \"content\": \"Azure Database for MariaDB is a fully managed, scalable, and secure relational database service that enables you to build and manage MariaDB applications in Azure. It provides features like automatic backups, monitoring, and high availability. Database for MariaDB supports various data types, such as JSON, spatial, and full-text. You can use Azure Database for MariaDB to migrate your existing applications, build new applications, and ensure the performance and security of your data. It also integrates with other Azure services, such as Azure App Service and Azure Data Factory.\", \"@search.score\": 3.9424267, \"@search.reranker_score\": null, \"@search.highlights\": null, \"@search.captions\": null}, {\"id\": \"69\", \"title\": \"Azure SQL Managed Instance\", \"category\": \"Databases\", \"content\": \"Azure SQL Managed Instance is a fully managed, scalable, and secure SQL Server instance hosted in Azure. It provides features like automatic backups, monitoring, and high availability. SQL Managed Instance supports various data types, such as JSON, spatial, and full-text. You can use Azure SQL Managed Instance to migrate your existing applications, build new applications, and ensure the performance and security of your data. It also integrates with other Azure services, such as Azure App Service and Azure Data Factory.\", \"@search.score\": 3.2041788, \"@search.reranker_score\": null, \"@search.highlights\": null, \"@search.captions\": null}, {\"id\": \"66\", \"title\": \"Azure Database for MySQL\", \"category\": \"Databases\", \"content\": \"Azure Database for MySQL is a fully managed, scalable, and secure relational database service that enables you to build and manage MySQL applications in Azure. It provides features like automatic backups, monitoring, and high availability. Database for MySQL supports various data types, such as JSON, spatial, and full-text. You can use Azure Database for MySQL to migrate your existing applications, build new applications, and ensure the performance and security of your data. It also integrates with other Azure services, such as Azure App Service and Azure Data Factory.\", \"@search.score\": 3.1852448, \"@search.reranker_score\": null, \"@search.highlights\": null, \"@search.captions\": null}, {\"id\": \"67\", \"title\": \"Azure Database for PostgreSQL\", \"category\": \"Databases\", \"content\": \"Azure Database for PostgreSQL is a fully managed, scalable, and secure relational database service that enables you to build and manage PostgreSQL applications in Azure. It provides features like automatic backups, monitoring, and high availability. Database for PostgreSQL supports various data types, such as JSON, spatial, and full-text. You can use Azure Database for PostgreSQL to migrate your existing applications, build new applications, and ensure the performance and security of your data. It also integrates with other Azure services, such as Azure App Service and Azure Data Factory.\", \"@search.score\": 2.8028796, \"@search.reranker_score\": null, \"@search.highlights\": null, \"@search.captions\": null}, {\"id\": \"3\", \"title\": \"Azure Cognitive Services\", \"category\": \"AI + Machine Learning\", \"content\": \"Azure Cognitive Services are a set of AI services that enable you to build intelligent applications with powerful algorithms using just a few lines of code. These services cover a wide range of capabilities, including vision, speech, language, knowledge, and search. They are designed to be easy to use and integrate into your applications. Cognitive Services are fully managed, scalable, and continuously improved by Microsoft. It allows developers to create AI-powered solutions without deep expertise in machine learning.\", \"@search.score\": 1.9905571, \"@search.reranker_score\": null, \"@search.highlights\": null, \"@search.captions\": null}]\n", + "\u001b[32m**********************************************************************\u001b[0m\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mCOGSearch\u001b[0m (to User):\n", + "\n", + "Here are the search results for \"What is Azure?\" from the index:\n", + "\n", + "1. **Azure Cognitive Search**\n", + " - Category: AI + Machine Learning\n", + " - Content: Azure Cognitive Search is a fully managed search-as-a-service that enables you to build rich search experiences for your applications. It provides features like full-text search, faceted navigation, and filters. Azure Cognitive Search supports various data sources, such as Azure SQL Database, Azure Blob Storage, and Azure Cosmos DB. You can use Azure Cognitive Search to index your data, create custom scoring profiles, and integrate with other Azure services. It also integrates with Azure Cognitive Services and Azure Machine Learning.\n", + " - Search Score: 9.1308\n", + "\n", + "2. **Azure Cognitive Services**\n", + " - Category: AI + Machine Learning\n", + " - Content: Azure Cognitive Services is a collection of AI services and APIs that enable you to build intelligent applications using pre-built models and algorithms. It provides features like computer vision, speech recognition, and natural language processing. Cognitive Services supports various platforms, such as .NET, Java, Node.js, and Python. You can use Azure Cognitive Services to build chatbots, analyze images and videos, and process and understand text. It also integrates with other Azure services, such as Azure Machine Learning and Azure Cognitive Search.\n", + " - Search Score: 5.9858904\n", + "\n", + "3. **Azure Database for MariaDB**\n", + " - Category: Databases\n", + " - Content: Azure Database for MariaDB is a fully managed, scalable, and secure relational database service that enables you to build and manage MariaDB applications in Azure. It provides features like automatic backups, monitoring, and high availability. Database for MariaDB supports various data types, such as JSON, spatial, and full-text. You can use Azure Database for MariaDB to migrate your existing applications, build new applications, and ensure the performance and security of your data. It also integrates with other Azure services, such as Azure App Service and Azure Data Factory.\n", + " - Search Score: 3.9424267\n", + "\n", + "4. **Azure SQL Managed Instance**\n", + " - Category: Databases\n", + " - Content: Azure SQL Managed Instance is a fully managed, scalable, and secure SQL Server instance hosted in Azure. It provides features like automatic backups, monitoring, and high availability. SQL Managed Instance supports various data types, such as JSON, spatial, and full-text. You can use Azure SQL Managed Instance to migrate your existing applications, build new applications, and ensure the performance and security of your data. It also integrates with other Azure services, such as Azure App Service and Azure Data Factory.\n", + " - Search Score: 3.2041788\n", + "\n", + "5. **Azure Database for MySQL**\n", + " - Category: Databases\n", + " - Content: Azure Database for MySQL is a fully managed, scalable, and secure relational database service that enables you to build and manage MySQL applications in Azure. It provides features like automatic backups, monitoring, and high availability. Database for MySQL supports various data types, such as JSON, spatial, and full-text. You can use Azure Database for MySQL to migrate your existing applications, build new applications, and ensure the performance and security of your data. It also integrates with other Azure services, such as Azure App Service and Azure Data Factory.\n", + " - Search Score: 3.1852448\n", + "\n", + "6. **Azure Database for PostgreSQL**\n", + " - Category: Databases\n", + " - Content: Azure Database for PostgreSQL is a fully managed, scalable, and secure relational database service that enables you to build and manage PostgreSQL applications in Azure. It provides features like automatic backups, monitoring, and high availability. Database for PostgreSQL supports various data types, such as JSON, spatial, and full-text. You can use Azure Database for PostgreSQL to migrate your existing applications, build new applications, and ensure the performance and security of your data. It also integrates with other Azure services, such as Azure App Service and Azure Data Factory.\n", + " - Search Score: 2.8028796\n", + "\n", + "7. **Azure Cognitive Services**\n", + " - Category: AI + Machine Learning\n", + " - Content: Azure Cognitive Services are a set of AI services that enable you to build intelligent applications with powerful algorithms using just a few lines of code. These services cover a wide range of capabilities, including vision, speech, language, knowledge, and search. They are designed to be easy to use and integrate into your applications. Cognitive Services are fully managed, scalable, and continuously improved by Microsoft. It allows developers to create AI-powered solutions without deep expertise in machine learning.\n", + " - Search Score: 1.9905571\n", + "\n", + "The search scores indicate the relevance of each result to the query \"What is Azure?\" with higher scores representing greater relevance. The top result provides a detailed explanation of Azure Cognitive Search, which is a part of the Azure platform.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mUser\u001b[0m (to COGSearch):\n", + "\n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mCOGSearch\u001b[0m (to User):\n", + "\n", + "TERMINATE\n", + "\n", + "--------------------------------------------------------------------------------\n" + ] + } + ], + "source": [ + "if __name__ == \"__main__\":\n", + " import asyncio\n", + "\n", + " async def main():\n", + " with Cache.disk() as cache:\n", + " await user_proxy.a_initiate_chat(\n", + " cog_search,\n", + " message=\"Search for 'What is Azure?' in the 'test-index' index\",\n", + " cache=cache,\n", + " )\n", + "\n", + " await main()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "front_matter": { + "description": "This notebook demonstrates the use of Assistant Agents in conjunction with Azure Cognitive Search and Azure Identity", + "tags": [ + "RAG", + "Azure Identity", + "Azure AI Search" + ] + }, + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.3" + }, + "skip_test": "This requires Azure AI Search to be enabled and creds for AI Search from Azure Portal" + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/notebook/agentchat_capability_long_context_handling.ipynb b/notebook/agentchat_capability_long_context_handling.ipynb index 7d2ef12a561..0bc1b4ffdd7 100644 --- a/notebook/agentchat_capability_long_context_handling.ipynb +++ b/notebook/agentchat_capability_long_context_handling.ipynb @@ -6,6 +6,10 @@ "source": [ "# Handling A Long Context via `TransformChatHistory`\n", "\n", + "
\n", + " Deprecation Notice: TransformChatHistory is no longer supported. Please use TransformMessages as the new standard method. For the latest examples, visit the notebook at notebook/agentchat_transform_messages.ipynb.\n", + "
\n", + "\n", "This notebook illustrates how you can use the `TransformChatHistory` capability to give any `Conversable` agent an ability to handle a long context. \n", "\n", "````{=mdx}\n", @@ -27,6 +31,7 @@ "outputs": [], "source": [ "import os\n", + "\n", "import autogen\n", "from autogen.agentchat.contrib.capabilities import context_handling" ] @@ -659,13 +664,6 @@ } ], "metadata": { - "front_matter": { - "description": "Use the TransformChatHistory capability to handle long contexts", - "tags": [ - "long context handling", - "capability" - ] - }, "kernelspec": { "display_name": "Python 3", "language": "python", diff --git a/notebook/agentchat_compression.ipynb b/notebook/agentchat_compression.ipynb index afdd20d356f..29cc2d9e224 100644 --- a/notebook/agentchat_compression.ipynb +++ b/notebook/agentchat_compression.ipynb @@ -853,10 +853,6 @@ } ], "metadata": { - "front_matter": { - "description": "Learn about the CompressibleAgent", - "tags": [] - }, "kernelspec": { "display_name": "msft", "language": "python", diff --git a/notebook/agentchat_cost_token_tracking.ipynb b/notebook/agentchat_cost_token_tracking.ipynb index 491fa1b1e40..7feb7a908f4 100644 --- a/notebook/agentchat_cost_token_tracking.ipynb +++ b/notebook/agentchat_cost_token_tracking.ipynb @@ -32,7 +32,7 @@ "To gather usage data for a list of agents, we provide an utility function `autogen.gather_usage_summary(agents)` where you pass in a list of agents and gather the usage summary.\n", "\n", "## Caution when using Azure OpenAI!\n", - "If you are using azure OpenAI, the model returned from completion doesn't have the version information. The returned model is either 'gpt-35-turbo' or 'gpt-4'. From there, we are calculating the cost based on gpt-3.5-0613: ((0.0015, 0.002) per 1k prompt and completion tokens) and gpt-4-0613: (0.03,0.06). This means the cost is wrong if you are using the 1106 version of the models from azure OpenAI.\n", + "If you are using azure OpenAI, the model returned from completion doesn't have the version information. The returned model is either 'gpt-35-turbo' or 'gpt-4'. From there, we are calculating the cost based on gpt-3.5-turbo-0125: (0.0005, 0.0015) per 1k prompt and completion tokens and gpt-4-0613: (0.03, 0.06). This means the cost can be wrong if you are using a different version from azure OpenAI.\n", "\n", "This will be improved in the future. However, the token count summary is accurate. You can use the token count to calculate the cost yourself.\n", "\n", @@ -55,27 +55,18 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import autogen\n", - "from autogen import OpenAIWrapper\n", - "from autogen import AssistantAgent, UserProxyAgent\n", - "from autogen import gather_usage_summary\n", - "\n", - "# config_list = autogen.config_list_from_json(\n", - "# \"OAI_CONFIG_LIST\",\n", - "# filter_dict={\n", - "# \"model\": [\"gpt-3.5-turbo\", \"gpt-4-1106-preview\"],\n", - "# },\n", - "# )\n", + "from autogen import AssistantAgent, OpenAIWrapper, UserProxyAgent, gather_usage_summary\n", "\n", "config_list = autogen.config_list_from_json(\n", " \"OAI_CONFIG_LIST\",\n", - " # filter_dict={\n", - " # \"model\": [\"gpt-3.5-turbo\", \"gpt-35-turbo\"],\n", - " # },\n", + " filter_dict={\n", + " \"tags\": [\"gpt-3.5-turbo\", \"gpt-3.5-turbo-16k\"], # comment out to get all\n", + " },\n", ")" ] }, @@ -83,21 +74,23 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "It first looks for environment variable \"OAI_CONFIG_LIST\" which needs to be a valid json string. If that variable is not found, it then looks for a json file named \"OAI_CONFIG_LIST\". It filters the configs by models (you can filter by other keys as well).\n", + "It first looks for environment variable \"OAI_CONFIG_LIST\" which needs to be a valid json string. If that variable is not found, it then looks for a json file named \"OAI_CONFIG_LIST\". It filters the configs by tags (you can filter by other keys as well).\n", "\n", "The config list looks like the following:\n", "```python\n", "config_list = [\n", " {\n", - " \"model\": \"gpt-4\",\n", + " \"model\": \"gpt-3.5-turbo\",\n", " \"api_key\": \"\",\n", - " }, # OpenAI API endpoint for gpt-4\n", + " \"tags\": [\"gpt-3.5-turbo\"],\n", + " }, # OpenAI API endpoint for gpt-3.5-turbo\n", " {\n", " \"model\": \"gpt-35-turbo-0613\", # 0613 or newer is needed to use functions\n", " \"base_url\": \"\", \n", " \"api_type\": \"azure\", \n", " \"api_version\": \"2024-02-15-preview\", # 2023-07-01-preview or newer is needed to use functions\n", - " \"api_key\": \"\"\n", + " \"api_key\": \"\",\n", + " \"tags\": [\"gpt-3.5-turbo\", \"0613\"],\n", " }\n", "]\n", "```\n", @@ -114,14 +107,14 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "0.00861\n" + "0.00020600000000000002\n" ] } ], @@ -130,7 +123,7 @@ "messages = [\n", " {\"role\": \"user\", \"content\": \"Can you give me 3 useful tips on learning Python? Keep it simple and short.\"},\n", "]\n", - "response = client.create(messages=messages, model=\"gpt-3.5-turbo\", cache_seed=None)\n", + "response = client.create(messages=messages, cache_seed=None)\n", "print(response.cost)" ] }, @@ -145,7 +138,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 3, "metadata": {}, "outputs": [ { @@ -166,7 +159,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 4, "metadata": {}, "outputs": [ { @@ -174,19 +167,21 @@ "output_type": "stream", "text": [ "----------------------------------------------------------------------------------------------------\n", - "No actual cost incurred (all completions are using cache).\n", + "Usage summary excluding cached usage: \n", + "Total cost: 0.00023\n", + "* Model 'gpt-35-turbo': cost: 0.00023, prompt_tokens: 25, completion_tokens: 142, total_tokens: 167\n", "\n", - "Usage summary including cached usage: \n", - "Total cost: 0.01059\n", - "* Model 'gpt-4': cost: 0.01059, prompt_tokens: 25, completion_tokens: 164, total_tokens: 189\n", + "All completions are non-cached: the total cost with cached completions is the same as actual cost.\n", "----------------------------------------------------------------------------------------------------\n", "----------------------------------------------------------------------------------------------------\n", - "No actual cost incurred (all completions are using cache).\n", + "Usage summary excluding cached usage: \n", + "Total cost: 0.00023\n", + "* Model 'gpt-35-turbo': cost: 0.00023, prompt_tokens: 25, completion_tokens: 142, total_tokens: 167\n", "----------------------------------------------------------------------------------------------------\n", "----------------------------------------------------------------------------------------------------\n", "Usage summary including cached usage: \n", - "Total cost: 0.01059\n", - "* Model 'gpt-4': cost: 0.01059, prompt_tokens: 25, completion_tokens: 164, total_tokens: 189\n", + "Total cost: 0.00023\n", + "* Model 'gpt-35-turbo': cost: 0.00023, prompt_tokens: 25, completion_tokens: 142, total_tokens: 167\n", "----------------------------------------------------------------------------------------------------\n" ] } @@ -194,7 +189,7 @@ "source": [ "# The first creation\n", "# By default, cache_seed is set to 41 and enabled. If you don't want to use cache, set cache_seed to None.\n", - "response = client.create(messages=messages, model=\"gpt-35-turbo-1106\", cache_seed=41)\n", + "response = client.create(messages=messages, cache_seed=41)\n", "client.print_usage_summary() # default to [\"actual\", \"total\"]\n", "client.print_usage_summary(mode=\"actual\") # print actual usage summary\n", "client.print_usage_summary(mode=\"total\") # print total usage summary" @@ -202,15 +197,15 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "None\n", - "{'total_cost': 0.01059, 'gpt-4': {'cost': 0.01059, 'prompt_tokens': 25, 'completion_tokens': 164, 'total_tokens': 189}}\n" + "{'total_cost': 0.0002255, 'gpt-35-turbo': {'cost': 0.0002255, 'prompt_tokens': 25, 'completion_tokens': 142, 'total_tokens': 167}}\n", + "{'total_cost': 0.0002255, 'gpt-35-turbo': {'cost': 0.0002255, 'prompt_tokens': 25, 'completion_tokens': 142, 'total_tokens': 167}}\n" ] } ], @@ -222,7 +217,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 6, "metadata": {}, "outputs": [ { @@ -230,11 +225,13 @@ "output_type": "stream", "text": [ "----------------------------------------------------------------------------------------------------\n", - "No actual cost incurred (all completions are using cache).\n", + "Usage summary excluding cached usage: \n", + "Total cost: 0.00023\n", + "* Model 'gpt-35-turbo': cost: 0.00023, prompt_tokens: 25, completion_tokens: 142, total_tokens: 167\n", "\n", "Usage summary including cached usage: \n", - "Total cost: 0.02118\n", - "* Model 'gpt-4': cost: 0.02118, prompt_tokens: 50, completion_tokens: 328, total_tokens: 378\n", + "Total cost: 0.00045\n", + "* Model 'gpt-35-turbo': cost: 0.00045, prompt_tokens: 50, completion_tokens: 284, total_tokens: 334\n", "----------------------------------------------------------------------------------------------------\n" ] } @@ -242,13 +239,13 @@ "source": [ "# Since cache is enabled, the same completion will be returned from cache, which will not incur any actual cost.\n", "# So actual cost doesn't change but total cost doubles.\n", - "response = client.create(messages=messages, model=\"gpt-35-turbo-1106\", cache_seed=41)\n", + "response = client.create(messages=messages, cache_seed=41)\n", "client.print_usage_summary()" ] }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 7, "metadata": {}, "outputs": [ { @@ -267,7 +264,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 8, "metadata": {}, "outputs": [ { @@ -278,15 +275,15 @@ "No actual cost incurred (all completions are using cache).\n", "\n", "Usage summary including cached usage: \n", - "Total cost: 0.01059\n", - "* Model 'gpt-4': cost: 0.01059, prompt_tokens: 25, completion_tokens: 164, total_tokens: 189\n", + "Total cost: 0.00023\n", + "* Model 'gpt-35-turbo': cost: 0.00023, prompt_tokens: 25, completion_tokens: 142, total_tokens: 167\n", "----------------------------------------------------------------------------------------------------\n" ] } ], "source": [ "# all completions are returned from cache, so no actual cost incurred.\n", - "response = client.create(messages=messages, model=\"gpt-35-turbo-1106\", cache_seed=41)\n", + "response = client.create(messages=messages, cache_seed=41)\n", "client.print_usage_summary()" ] }, @@ -304,7 +301,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 9, "metadata": {}, "outputs": [ { @@ -315,32 +312,22 @@ "\n", "$x^3=125$. What is x?\n", "\n", - "--------------------------------------------------------------------------------\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ + "--------------------------------------------------------------------------------\n", "\u001b[33massistant\u001b[0m (to ai_user):\n", "\n", - "To find the value of $x$ when $x^3 = 125$, you can find the cube root of 125. The cube root of a number is a value that, when multiplied by itself three times, gives the original number.\n", - "\n", - "The cube root of 125 can be written as $125^{1/3}$ or $\\sqrt[3]{125}$. Since $5 \\times 5 \\times 5 = 125$, it follows that:\n", - "\n", - "$$x = \\sqrt[3]{125} = 5$$\n", + "To find x, we need to take the cube root of 125. The cube root of a number is the number that, when multiplied by itself three times, gives the original number.\n", "\n", - "Therefore, $x = 5$.\n", + "In this case, the cube root of 125 is 5 since 5 * 5 * 5 = 125. Therefore, x = 5.\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mai_user\u001b[0m (to assistant):\n", "\n", - "Your calculation is correct. The value of $x$ when $x^3 = 125$ is indeed $x = 5$. Great job!\n", + "That's correct! Well done. The value of x is indeed 5, as you correctly found by taking the cube root of 125. Keep up the good work!\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33massistant\u001b[0m (to ai_user):\n", "\n", - "Thank you for the confirmation! I'm glad the answer was helpful. If you have any more questions or need assistance with anything else, feel free to ask!\n", + "Thank you! I'm glad I could help. If you have any more questions, feel free to ask!\n", "\n", "--------------------------------------------------------------------------------\n" ] @@ -348,10 +335,10 @@ { "data": { "text/plain": [ - "ChatResult(chat_history=[{'content': '$x^3=125$. What is x?', 'role': 'assistant'}, {'content': 'To find the value of $x$ when $x^3 = 125$, you can find the cube root of 125. The cube root of a number is a value that, when multiplied by itself three times, gives the original number.\\n\\nThe cube root of 125 can be written as $125^{1/3}$ or $\\\\sqrt[3]{125}$. Since $5 \\\\times 5 \\\\times 5 = 125$, it follows that:\\n\\n$$x = \\\\sqrt[3]{125} = 5$$\\n\\nTherefore, $x = 5$.', 'role': 'user'}, {'content': 'Your calculation is correct. The value of $x$ when $x^3 = 125$ is indeed $x = 5$. Great job!', 'role': 'assistant'}, {'content': \"Thank you for the confirmation! I'm glad the answer was helpful. If you have any more questions or need assistance with anything else, feel free to ask!\", 'role': 'user'}], summary=\"Thank you for the confirmation! I'm glad the answer was helpful. If you have any more questions or need assistance with anything else, feel free to ask!\", cost=({'total_cost': 0.022019999999999998, 'gpt-4': {'cost': 0.022019999999999998, 'prompt_tokens': 372, 'completion_tokens': 181, 'total_tokens': 553}}, {'total_cost': 0.022019999999999998, 'gpt-4': {'cost': 0.022019999999999998, 'prompt_tokens': 372, 'completion_tokens': 181, 'total_tokens': 553}}), human_input=[])" + "ChatResult(chat_id=None, chat_history=[{'content': '$x^3=125$. What is x?', 'role': 'assistant'}, {'content': 'To find x, we need to take the cube root of 125. The cube root of a number is the number that, when multiplied by itself three times, gives the original number.\\n\\nIn this case, the cube root of 125 is 5 since 5 * 5 * 5 = 125. Therefore, x = 5.', 'role': 'user'}, {'content': \"That's correct! Well done. The value of x is indeed 5, as you correctly found by taking the cube root of 125. Keep up the good work!\", 'role': 'assistant'}, {'content': \"Thank you! I'm glad I could help. If you have any more questions, feel free to ask!\", 'role': 'user'}], summary=\"Thank you! I'm glad I could help. If you have any more questions, feel free to ask!\", cost={'usage_including_cached_inference': {'total_cost': 0.000333, 'gpt-35-turbo': {'cost': 0.000333, 'prompt_tokens': 282, 'completion_tokens': 128, 'total_tokens': 410}}, 'usage_excluding_cached_inference': {'total_cost': 0.000333, 'gpt-35-turbo': {'cost': 0.000333, 'prompt_tokens': 282, 'completion_tokens': 128, 'total_tokens': 410}}}, human_input=[])" ] }, - "execution_count": 22, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -389,7 +376,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 10, "metadata": {}, "outputs": [ { @@ -399,8 +386,8 @@ "Agent 'ai_user':\n", "----------------------------------------------------------------------------------------------------\n", "Usage summary excluding cached usage: \n", - "Total cost: 0.00669\n", - "* Model 'gpt-4': cost: 0.00669, prompt_tokens: 161, completion_tokens: 31, total_tokens: 192\n", + "Total cost: 0.00011\n", + "* Model 'gpt-35-turbo': cost: 0.00011, prompt_tokens: 114, completion_tokens: 35, total_tokens: 149\n", "\n", "All completions are non-cached: the total cost with cached completions is the same as actual cost.\n", "----------------------------------------------------------------------------------------------------\n", @@ -408,8 +395,8 @@ "Agent 'assistant':\n", "----------------------------------------------------------------------------------------------------\n", "Usage summary excluding cached usage: \n", - "Total cost: 0.01533\n", - "* Model 'gpt-4': cost: 0.01533, prompt_tokens: 211, completion_tokens: 150, total_tokens: 361\n", + "Total cost: 0.00022\n", + "* Model 'gpt-35-turbo': cost: 0.00022, prompt_tokens: 168, completion_tokens: 93, total_tokens: 261\n", "\n", "All completions are non-cached: the total cost with cached completions is the same as actual cost.\n", "----------------------------------------------------------------------------------------------------\n" @@ -424,7 +411,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 11, "metadata": {}, "outputs": [ { @@ -448,17 +435,17 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Actual usage summary for assistant (excluding completion from cache): {'total_cost': 0.01533, 'gpt-4': {'cost': 0.01533, 'prompt_tokens': 211, 'completion_tokens': 150, 'total_tokens': 361}}\n", - "Total usage summary for assistant (including completion from cache): {'total_cost': 0.01533, 'gpt-4': {'cost': 0.01533, 'prompt_tokens': 211, 'completion_tokens': 150, 'total_tokens': 361}}\n", - "Actual usage summary for ai_user_proxy: {'total_cost': 0.00669, 'gpt-4': {'cost': 0.00669, 'prompt_tokens': 161, 'completion_tokens': 31, 'total_tokens': 192}}\n", - "Total usage summary for ai_user_proxy: {'total_cost': 0.00669, 'gpt-4': {'cost': 0.00669, 'prompt_tokens': 161, 'completion_tokens': 31, 'total_tokens': 192}}\n", + "Actual usage summary for assistant (excluding completion from cache): {'total_cost': 0.0002235, 'gpt-35-turbo': {'cost': 0.0002235, 'prompt_tokens': 168, 'completion_tokens': 93, 'total_tokens': 261}}\n", + "Total usage summary for assistant (including completion from cache): {'total_cost': 0.0002235, 'gpt-35-turbo': {'cost': 0.0002235, 'prompt_tokens': 168, 'completion_tokens': 93, 'total_tokens': 261}}\n", + "Actual usage summary for ai_user_proxy: {'total_cost': 0.0001095, 'gpt-35-turbo': {'cost': 0.0001095, 'prompt_tokens': 114, 'completion_tokens': 35, 'total_tokens': 149}}\n", + "Total usage summary for ai_user_proxy: {'total_cost': 0.0001095, 'gpt-35-turbo': {'cost': 0.0001095, 'prompt_tokens': 114, 'completion_tokens': 35, 'total_tokens': 149}}\n", "Actual usage summary for user_proxy: None\n", "Total usage summary for user_proxy: None\n" ] @@ -477,27 +464,27 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "{'total_cost': 0.022019999999999998,\n", - " 'gpt-4': {'cost': 0.022019999999999998,\n", - " 'prompt_tokens': 372,\n", - " 'completion_tokens': 181,\n", - " 'total_tokens': 553}}" + "{'total_cost': 0.000333,\n", + " 'gpt-35-turbo': {'cost': 0.000333,\n", + " 'prompt_tokens': 282,\n", + " 'completion_tokens': 128,\n", + " 'total_tokens': 410}}" ] }, - "execution_count": 26, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "total_usage_summary, actual_usage_summary = gather_usage_summary([assistant, ai_user_proxy, user_proxy])\n", - "total_usage_summary" + "usage_summary = gather_usage_summary([assistant, ai_user_proxy, user_proxy])\n", + "usage_summary[\"usage_including_cached_inference\"]" ] } ], diff --git a/notebook/agentchat_custom_model.ipynb b/notebook/agentchat_custom_model.ipynb index d4cf14e57a0..b06d2c3cf4e 100644 --- a/notebook/agentchat_custom_model.ipynb +++ b/notebook/agentchat_custom_model.ipynb @@ -36,10 +36,12 @@ "metadata": {}, "outputs": [], "source": [ + "from types import SimpleNamespace\n", + "\n", + "from transformers import AutoModelForCausalLM, AutoTokenizer, GenerationConfig\n", + "\n", "import autogen\n", - "from autogen import AssistantAgent, UserProxyAgent\n", - "from transformers import AutoTokenizer, GenerationConfig, AutoModelForCausalLM\n", - "from types import SimpleNamespace" + "from autogen import AssistantAgent, UserProxyAgent" ] }, { diff --git a/notebook/agentchat_dalle_and_gpt4v.ipynb b/notebook/agentchat_dalle_and_gpt4v.ipynb index dd84ad238ad..258b49d6976 100644 --- a/notebook/agentchat_dalle_and_gpt4v.ipynb +++ b/notebook/agentchat_dalle_and_gpt4v.ipynb @@ -46,7 +46,7 @@ "\n", "import autogen\n", "from autogen import Agent, AssistantAgent, ConversableAgent, UserProxyAgent\n", - "from autogen.agentchat.contrib.img_utils import _to_pil, get_image_data, gpt4v_formatter, get_pil_image\n", + "from autogen.agentchat.contrib.img_utils import _to_pil, get_image_data, get_pil_image, gpt4v_formatter\n", "from autogen.agentchat.contrib.multimodal_conversable_agent import MultimodalConversableAgent" ] }, diff --git a/notebook/agentchat_function_call.ipynb b/notebook/agentchat_function_call.ipynb index 1ae6dd81b74..c91699d0d44 100644 --- a/notebook/agentchat_function_call.ipynb +++ b/notebook/agentchat_function_call.ipynb @@ -31,7 +31,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 10, "id": "2b803c17", "metadata": {}, "outputs": [], @@ -52,7 +52,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 11, "id": "dca301a4", "metadata": {}, "outputs": [], @@ -65,9 +65,7 @@ "\n", "config_list = autogen.config_list_from_json(\n", " \"OAI_CONFIG_LIST\",\n", - " filter_dict={\n", - " \"model\": [\"gpt-4\", \"gpt-3.5-turbo\", \"gpt-3.5-turbo-16k\"],\n", - " },\n", + " filter_dict={\"tags\": [\"tool\"]}, # comment out to get all\n", ")" ] }, @@ -77,7 +75,7 @@ "id": "92fde41f", "metadata": {}, "source": [ - "It first looks for environment variable \"OAI_CONFIG_LIST\" which needs to be a valid json string. If that variable is not found, it then looks for a json file named \"OAI_CONFIG_LIST\". It filters the configs by models (you can filter by other keys as well). Only the models with matching names are kept in the list based on the filter condition.\n", + "It first looks for environment variable \"OAI_CONFIG_LIST\" which needs to be a valid json string. If that variable is not found, it then looks for a json file named \"OAI_CONFIG_LIST\". It filters the configs by tags (you can filter by other keys as well). Only the configs with matching tags are kept in the list based on the filter condition.\n", "\n", "The config list looks like the following:\n", "```python\n", @@ -85,6 +83,7 @@ " {\n", " 'model': 'gpt-4',\n", " 'api_key': '',\n", + " 'tags': ['tool', 'gpt-4'],\n", " },\n", " {\n", " 'model': 'gpt-3.5-turbo',\n", @@ -92,6 +91,7 @@ " 'base_url': '',\n", " 'api_type': 'azure',\n", " 'api_version': '2024-02-15-preview',\n", + " 'tags': ['tool', 'gpt-3.5-turbo'],\n", " },\n", " {\n", " 'model': 'gpt-3.5-turbo-16k',\n", @@ -99,6 +99,7 @@ " 'base_url': '',\n", " 'api_type': 'azure',\n", " 'api_version': '2024-02-15-preview',\n", + " 'tags': ['tool', 'gpt-3.5-turbo-16k'],\n", " },\n", "]\n", "```\n", @@ -119,7 +120,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 12, "id": "9fb85afb", "metadata": {}, "outputs": [], @@ -188,7 +189,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 13, "id": "27d3e43a", "metadata": {}, "outputs": [ @@ -203,62 +204,9 @@ "--------------------------------------------------------------------------------\n", "\u001b[33mchatbot\u001b[0m (to user_proxy):\n", "\n", - "\u001b[32m***** Suggested tool Call (call_bsaGbd8WGdC869LhG62hI0uK): python *****\u001b[0m\n", - "Arguments: \n", - "cell = \"\"\"\n", - "import matplotlib.pyplot as plt\n", - "import matplotlib.patches as patches\n", - "\n", - "# Creating a simple scene for two agents chatting\n", - "fig, ax = plt.subplots()\n", - "\n", - "# Draw two circles representing the agents\n", - "ax.add_patch(patches.Circle((2, 2), 0.5, fill=True, color='blue', label='Agent A'))\n", - "ax.add_patch(patches.Circle((5, 2), 0.5, fill=True, color='green', label='Agent B'))\n", - "\n", - "# Example dialogues as text\n", - "ax.text(1, 3, \"Hello!\", style='italic', bbox={'facecolor': 'red', 'alpha': 0.5, 'pad': 5})\n", - "ax.text(4, 3, \"Hi there!\", style='italic', bbox={'facecolor': 'yellow', 'alpha': 0.5, 'pad': 5})\n", - "\n", - "# Setting the limits of the plot\n", - "ax.set_xlim(0, 7)\n", - "ax.set_ylim(0, 4)\n", - "\n", - "# Hiding the axes\n", - "ax.axis('off')\n", - "\n", - "# Use this line just before the plt.show() if necessary\n", - "plt.savefig(\"agents_chatting.png\")\n", - "\n", - "# Don't add plt.show() as per the instructions\n", - "\"\"\"\n", - "return cell\n", - "\u001b[32m***********************************************************************\u001b[0m\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33muser_proxy\u001b[0m (to chatbot):\n", - "\n", - "\u001b[33muser_proxy\u001b[0m (to chatbot):\n", - "\n", - "\u001b[32m***** Response from calling tool \"call_bsaGbd8WGdC869LhG62hI0uK\" *****\u001b[0m\n", - "Error: Expecting value: line 1 column 1 (char 0)\n", - " You argument should follow json format.\n", - "\u001b[32m**********************************************************************\u001b[0m\n", - "\n", - "--------------------------------------------------------------------------------\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[33mchatbot\u001b[0m (to user_proxy):\n", - "\n", - "\u001b[32m***** Suggested tool Call (call_ujcz2CkK0UgEEUen7X1ctXhe): python *****\u001b[0m\n", + "\u001b[32m***** Suggested tool call (call_BiLzujDvfB7WMZ0hqcgBdjN2): python *****\u001b[0m\n", "Arguments: \n", - "{\n", - " \"cell\": \"import matplotlib.pyplot as plt\\nimport matplotlib.patches as patches\\n\\n# Creating a simple scene for two agents chatting\\nfig, ax = plt.subplots()\\n\\n# Draw two circles representing the agents\\nax.add_patch(patches.Circle((2, 2), 0.5, fill=True, color='blue', label='Agent A'))\\nax.add_patch(patches.Circle((5, 2), 0.5, fill=True, color='green', label='Agent B'))\\n\\n# Example dialogues as text\\nax.text(1, 3, \\\"Hello!\\\", style='italic', bbox={'facecolor': 'red', 'alpha': 0.5, 'pad': 5})\\nax.text(4, 3, \\\"Hi there!\\\", style='italic', bbox={'facecolor': 'yellow', 'alpha': 0.5, 'pad': 5})\\n\\n# Setting the limits of the plot\\nax.set_xlim(0, 7)\\nax.set_ylim(0, 4)\\n\\n# Hiding the axes\\nax.axis('off')\\n\\n# Use this line just before the plt.show() if necessary\\nplt.savefig(\\\"agents_chatting.png\\\")\\n\\n# Don't add plt.show() as per the instructions\\n\"\n", - "}\n", + "{\"cell\":\"import matplotlib.pyplot as plt\\nimport numpy as np\\n\\n# Create a simple representation of two agents\\nagent1_x, agent1_y = [1, 2], [1, 1]\\nagent2_x, agent2_y = [4, 3], [1, 1]\\n\\n# Create dialog bubbles\\nbubble1_x = np.linspace(1.5, 2.5, 100)\\nbubble1_y = np.sin(np.pi * (bubble1_x - 1.5)) + 1.2\\nbubble2_x = np.linspace(3.5, 2.5, 100)\\nbubble2_y = np.sin(np.pi * (bubble2_x - 2.5)) + 1.2\\n\\n# Drawing agents and dialog bubbles\\nplt.figure(figsize=(6, 3))\\nplt.plot(agent1_x, agent1_y, 'ko-', markersize=20)\\nplt.plot(agent2_x, agent2_y, 'ko-', markersize=20)\\nplt.plot(bubble1_x, bubble1_y, 'k')\\nplt.plot(bubble2_x, bubble2_y, 'k')\\nplt.fill_between(bubble1_x, 1, bubble1_y, color = 'grey', alpha = 0.5)\\nplt.fill_between(bubble2_x, 1, bubble2_y, color = 'grey', alpha = 0.5)\\n\\n# Example Dialog\\nplt.text(1.5, 1.5, 'Hi!', fontsize=12)\\nplt.text(3.5, 1.5, 'Hello!', fontsize=12)\\n\\nplt.xlim(0, 5)\\nplt.ylim(0, 2.5)\\nplt.axis('off')\\n\"}\n", "\u001b[32m***********************************************************************\u001b[0m\n", "\n", "--------------------------------------------------------------------------------\n", @@ -268,9 +216,19 @@ }, { "data": { - "image/png": "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", "text/plain": [ - "
" + "(0.0, 5.0, 0.0, 2.5)" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" ] }, "metadata": {}, @@ -284,14 +242,24 @@ "\n", "\u001b[33muser_proxy\u001b[0m (to chatbot):\n", "\n", - "\u001b[32m***** Response from calling tool \"call_ujcz2CkK0UgEEUen7X1ctXhe\" *****\u001b[0m\n", - "None\n", + "\u001b[32m***** Response from calling tool (call_BiLzujDvfB7WMZ0hqcgBdjN2) *****\u001b[0m\n", + "(0.0, 5.0, 0.0, 2.5)\n", "\u001b[32m**********************************************************************\u001b[0m\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mchatbot\u001b[0m (to user_proxy):\n", "\n", - "TERMINATE\n", + "I've drawn two agents chatting with each other, including example dialogues saying \"Hi!\" and \"Hello!\" within their speech bubbles. This visualization is created using matplotlib, and the conversation is represented graphically with the agents and their dialogue bubbles placed on a 2D plot. The drawing does not include `plt.show()`, adhering to your instruction.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33muser_proxy\u001b[0m (to chatbot):\n", + "\n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mchatbot\u001b[0m (to user_proxy):\n", + "\n", + "It seems your last message was empty. How can I assist you further?\n", "\n", "--------------------------------------------------------------------------------\n" ] @@ -304,16 +272,9 @@ " chatbot,\n", " message=\"Draw two agents chatting with each other with an example dialog. Don't add plt.show().\",\n", " cache=cache,\n", + " max_turns=3,\n", " )" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ab081090", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/notebook/agentchat_function_call_async.ipynb b/notebook/agentchat_function_call_async.ipynb index 00e3ee8c761..57233547ebc 100644 --- a/notebook/agentchat_function_call_async.ipynb +++ b/notebook/agentchat_function_call_async.ipynb @@ -44,7 +44,7 @@ "import autogen\n", "from autogen.cache import Cache\n", "\n", - "config_list = autogen.config_list_from_json(env_or_file=\"OAI_CONFIG_LIST\")" + "config_list = autogen.config_list_from_json(env_or_file=\"OAI_CONFIG_LIST\", filter_dict={\"tags\": [\"tool\"]})" ] }, { @@ -364,8 +364,8 @@ "front_matter": { "description": "Learn how to implement both synchronous and asynchronous function calls using AssistantAgent and UserProxyAgent in AutoGen, with examples of their application in individual and group chat settings for task execution with language models.", "tags": [ - "code generation", "function call", + "tool use", "async" ] }, @@ -384,7 +384,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.13" + "version": "3.10.14" } }, "nbformat": 4, diff --git a/notebook/agentchat_function_call_code_writing.ipynb b/notebook/agentchat_function_call_code_writing.ipynb new file mode 100644 index 00000000000..92074e4821b --- /dev/null +++ b/notebook/agentchat_function_call_code_writing.ipynb @@ -0,0 +1,1012 @@ +{ + "cells": [ + { + "attachments": {}, + "cell_type": "markdown", + "id": "9a71fa36", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "# Writing a software application using function calls\n", + "\n", + "The default way of creating code in Autogen is its built-in code extractor. Although it allows for creating and executing simple scripts fast, that way of creating code is not suitable for developing advanced software applications, according to my experiences. The process of developing an application is mostly the process of introducing changes into existing files rather than creating new files with code. And in my experience, the code extractor is bad at introducing changes as the model often gets lost and can damage existing files.\n", + "\n", + "Properly created functions that can modify code provide us with the ability to have more control over code changes and result in better quality. Additionally, as the scope of possible operations is predefined inside the tools, we can safely use Autogen without Docker, avoiding all the complications related to it.\n", + "\n", + "## Requirements" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c528cd6d", + "metadata": {}, + "outputs": [], + "source": [ + "! pip install pyautogen" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "5ebd2397", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "source": [ + "## Set your API Endpoint" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "dca301a4", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "import os\n", + "\n", + "import autogen\n", + "\n", + "config_list = [{\"model\": \"gpt-4-turbo-preview\", \"api_key\": os.getenv(\"OPENAI_API_KEY\")}]" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "2b9526e7", + "metadata": {}, + "source": [ + "## Create agents\n", + "\n", + "In this example, we will improve a simple FastAPI application using only dedicated function calls. Let's create an Engineer agent that will think out and execute code changes, and a user proxy Admin agent, through which we will guide our Engineer." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "1a10c9fe-1fbc-40c6-b655-5d2256864ce8", + "metadata": {}, + "outputs": [], + "source": [ + "llm_config = {\n", + " \"temperature\": 0,\n", + " \"config_list\": config_list,\n", + "}\n", + "\n", + "engineer = autogen.AssistantAgent(\n", + " name=\"Engineer\",\n", + " llm_config=llm_config,\n", + " system_message=\"\"\"\n", + " I'm Engineer. I'm expert in python programming. I'm executing code tasks required by Admin.\n", + " \"\"\",\n", + ")\n", + "\n", + "user_proxy = autogen.UserProxyAgent(\n", + " name=\"Admin\",\n", + " human_input_mode=\"ALWAYS\",\n", + " code_execution_config=False,\n", + ")" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "966c96a4-cc8a-4400-b8db-a21b7142e33c", + "metadata": {}, + "source": [ + "Mention, unlike in many other examples, here we don't need a separate Executor agent to save our code, as that will be done by functions. We also don't need Docker to be running because of that - which makes the entire process easier.\n", + "\n", + "Next, let's set up our group chat." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "354b4a8f-7a96-455b-9f17-cbc19d880462", + "metadata": {}, + "outputs": [], + "source": [ + "groupchat = autogen.GroupChat(\n", + " agents=[engineer, user_proxy],\n", + " messages=[],\n", + " max_round=500,\n", + " speaker_selection_method=\"round_robin\",\n", + " enable_clear_history=True,\n", + ")\n", + "manager = autogen.GroupChatManager(groupchat=groupchat, llm_config=llm_config)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "d7b0ad4c-a287-456d-9c0e-c4895e5f8ed2", + "metadata": {}, + "source": [ + "## Prepare appropriate functions\n", + "\n", + "Let's go to core of the thing. Prepare functions that provide Engineer with functionality to modify existing code, create new code files, check filesystem and files.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "94b85d81-bdc5-4c9c-a9da-59a796317731", + "metadata": {}, + "outputs": [], + "source": [ + "from typing_extensions import Annotated\n", + "\n", + "default_path = \"backend_dir/\"\n", + "\n", + "\n", + "@user_proxy.register_for_execution()\n", + "@engineer.register_for_llm(description=\"List files in choosen directory.\")\n", + "def list_dir(directory: Annotated[str, \"Directory to check.\"]):\n", + " files = os.listdir(default_path + directory)\n", + " return 0, files\n", + "\n", + "\n", + "@user_proxy.register_for_execution()\n", + "@engineer.register_for_llm(description=\"Check the contents of a chosen file.\")\n", + "def see_file(filename: Annotated[str, \"Name and path of file to check.\"]):\n", + " with open(default_path + filename, \"r\") as file:\n", + " lines = file.readlines()\n", + " formatted_lines = [f\"{i+1}:{line}\" for i, line in enumerate(lines)]\n", + " file_contents = \"\".join(formatted_lines)\n", + "\n", + " return 0, file_contents\n", + "\n", + "\n", + "@user_proxy.register_for_execution()\n", + "@engineer.register_for_llm(description=\"Replace old piece of code with new one. Proper indentation is important.\")\n", + "def modify_code(\n", + " filename: Annotated[str, \"Name and path of file to change.\"],\n", + " start_line: Annotated[int, \"Start line number to replace with new code.\"],\n", + " end_line: Annotated[int, \"End line number to replace with new code.\"],\n", + " new_code: Annotated[str, \"New piece of code to replace old code with. Remember about providing indents.\"],\n", + "):\n", + " with open(default_path + filename, \"r+\") as file:\n", + " file_contents = file.readlines()\n", + " file_contents[start_line - 1 : end_line] = [new_code + \"\\n\"]\n", + " file.seek(0)\n", + " file.truncate()\n", + " file.write(\"\".join(file_contents))\n", + " return 0, \"Code modified\"\n", + "\n", + "\n", + "@user_proxy.register_for_execution()\n", + "@engineer.register_for_llm(description=\"Create a new file with code.\")\n", + "def create_file_with_code(\n", + " filename: Annotated[str, \"Name and path of file to create.\"], code: Annotated[str, \"Code to write in the file.\"]\n", + "):\n", + " with open(default_path + filename, \"w\") as file:\n", + " file.write(code)\n", + " return 0, \"File created successfully\"" + ] + }, + { + "cell_type": "markdown", + "id": "8a3a09c9", + "metadata": {}, + "source": [ + "## Prepare code to work with\n", + "\n", + "In this example, we will show how AI can extend the functionalities of existing code, as improving existing code is a much more frequent use case in software development than creating a new one. Let's prepare the initial code on which we will work. That will be a simple FastAPI script that will allow you to calculate today's stock spread in percentage for CD Project Red, a famous Polish gamedev company. Create a folder called 'backend_dir' and place a 'main.py' file here with the following content:" + ] + }, + { + "cell_type": "markdown", + "id": "370a9f8d-d5ce-4127-8646-cf0e4effd9f5", + "metadata": {}, + "source": [ + "```python\n", + "# backend_dir/main.py\n", + "\n", + "from fastapi import FastAPI\n", + "import yfinance as yf\n", + "\n", + "app = FastAPI()\n", + "\n", + "@app.get(\"/cdr_daily_spread\")\n", + "async def calculate_daily_spread():\n", + " cdr = yf.Ticker(\"CDR.WA\")\n", + " today_data = cdr.history(period=\"1d\")\n", + " spread = ((today_data[\"High\"] - today_data[\"Low\"]) / today_data[\"Low\"]) * 100\n", + " return spread\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "945003b5-b764-4ef1-99d9-b9464e39dfed", + "metadata": {}, + "source": [ + "Install needed libraries. We can run our API using:\n", + "\n", + "```bash\n", + "uvicorn main:app --reload\n", + "```\n", + "\n", + "Send a request to 'localhost:8000/cdr_daily_spread' to check if it works.\n", + "\n", + "## Edit code using agents\n", + "\n", + "Let's assume we want our agents to extend the functionality of the application. Let's modify the endpoint to check the spread also for 11bits, another gamedev company, and compare it for both stocks. Also, let's separate the internal logic into a different file.\n", + "\n", + "Finally, instantiate a chat between the Engineer and the Admin. It will start by exploring the filesystem first, and after that, it will wait for our orders. Then, we will explain the task to the Engineer and ask him to provide a plan of changes first - according to my experience, that greatly increases the quality of LLM responses.\n", + "\n", + "After that, introduce changes with the Engineer one after another. Ask him to correct something or improve the functionality if needed. Do not hesitate to interrupt the tool's execution if you feel he is going to do something wrong. If errors occur, provide him with the error log and ask him to check out the file to refresh his knowledge about it before actually introducing changes." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "d5518947", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mAdmin\u001b[0m (to chat_manager):\n", + "\n", + "\n", + "You will need to improve app in FastApi. For now, check out all the application files, try to understand it and wait for next instructions.\n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mEngineer\u001b[0m (to chat_manager):\n", + "\n", + "\u001b[32m***** Suggested tool Call (call_SA61u9yCLhyXfd9NCV9TAIiM): list_dir *****\u001b[0m\n", + "Arguments: \n", + "\n", + "{\n", + " \"directory\": \"./\"\n", + "}\n", + "\u001b[32m*************************************************************************\u001b[0m\n", + "\n", + "--------------------------------------------------------------------------------\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Provide feedback to chat_manager. Press enter to skip and use auto-reply, or type 'exit' to end the conversation: \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[31m\n", + ">>>>>>>> NO HUMAN INPUT RECEIVED.\u001b[0m\n", + "\u001b[31m\n", + ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", + "\u001b[35m\n", + ">>>>>>>> EXECUTING FUNCTION list_dir...\u001b[0m\n", + "\u001b[33mAdmin\u001b[0m (to chat_manager):\n", + "\n", + "\u001b[33mAdmin\u001b[0m (to chat_manager):\n", + "\n", + "\u001b[32m***** Response from calling tool \"call_SA61u9yCLhyXfd9NCV9TAIiM\" *****\u001b[0m\n", + "[0, [\"main.py\", \"__pycache__\"]]\n", + "\u001b[32m**********************************************************************\u001b[0m\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mEngineer\u001b[0m (to chat_manager):\n", + "\n", + "\u001b[32m***** Suggested tool Call (call_IVJNPI12s4fCzysnWjExZjL2): see_file *****\u001b[0m\n", + "Arguments: \n", + "\n", + "{\n", + " \"filename\": \"main.py\"\n", + "}\n", + "\u001b[32m*************************************************************************\u001b[0m\n", + "\n", + "--------------------------------------------------------------------------------\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Provide feedback to chat_manager. Press enter to skip and use auto-reply, or type 'exit' to end the conversation: \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[31m\n", + ">>>>>>>> NO HUMAN INPUT RECEIVED.\u001b[0m\n", + "\u001b[31m\n", + ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", + "\u001b[35m\n", + ">>>>>>>> EXECUTING FUNCTION see_file...\u001b[0m\n", + "\u001b[33mAdmin\u001b[0m (to chat_manager):\n", + "\n", + "\u001b[33mAdmin\u001b[0m (to chat_manager):\n", + "\n", + "\u001b[32m***** Response from calling tool \"call_IVJNPI12s4fCzysnWjExZjL2\" *****\u001b[0m\n", + "[0, \"1:from fastapi import FastAPI\\n2:import yfinance as yf\\n3:\\n4:app = FastAPI()\\n5:\\n6:@app.get(\\\"/cdr_daily_spread\\\")\\n7:async def calculate_daily_spread():\\n8: cdr = yf.Ticker(\\\"CDR.WA\\\")\\n9: today_data = cdr.history(period=\\\"1d\\\")\\n10: spread = ((today_data[\\\"High\\\"] - today_data[\\\"Low\\\"]) / today_data[\\\"Low\\\"]) * 100\\n11: return spread\"]\n", + "\u001b[32m**********************************************************************\u001b[0m\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mEngineer\u001b[0m (to chat_manager):\n", + "\n", + "The application is a FastAPI application with a single endpoint `/cdr_daily_spread`. This endpoint calculates the daily spread of the stock \"CDR.WA\" using the `yfinance` library. The spread is calculated as the percentage difference between the high and low prices of the stock for the day.\n", + "\n", + "--------------------------------------------------------------------------------\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Provide feedback to chat_manager. Press enter to skip and use auto-reply, or type 'exit' to end the conversation: Let's move spread calculation logic to separate file. Create spread_calculation function with ticker argument in it. in our endpoint, we need to compare daily spread for CD Project Red and 11bits Studio - return difference between them. Start with providing plan of changes. Don't do any changes yet.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mAdmin\u001b[0m (to chat_manager):\n", + "\n", + "Let's move spread calculation logic to separate file. Create spread_calculation function with ticker argument in it. in our endpoint, we need to compare daily spread for CD Project Red and 11bits Studio - return difference between them. Start with providing plan of changes. Don't do any changes yet.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mEngineer\u001b[0m (to chat_manager):\n", + "\n", + "Sure, here is the plan of changes:\n", + "\n", + "1. Create a new Python file named `spread_calculation.py`.\n", + "2. In this file, define a function named `calculate_daily_spread` that takes a `ticker` as an argument. This function will contain the logic to calculate the daily spread of a given stock.\n", + "3. Modify the `main.py` file to import the `calculate_daily_spread` function from `spread_calculation.py`.\n", + "4. Modify the `/cdr_daily_spread` endpoint in `main.py` to calculate the daily spread for both \"CDR.WA\" (CD Project Red) and \"11B.WA\" (11bits Studio) by calling the `calculate_daily_spread` function with the respective ticker as argument.\n", + "5. The endpoint will then return the difference between the daily spreads of the two stocks.\n", + "\n", + "Please confirm if this plan is correct and I can proceed with the changes.\n", + "\n", + "--------------------------------------------------------------------------------\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Provide feedback to chat_manager. Press enter to skip and use auto-reply, or type 'exit' to end the conversation: go with the plan\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mAdmin\u001b[0m (to chat_manager):\n", + "\n", + "go with the plan\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mEngineer\u001b[0m (to chat_manager):\n", + "\n", + "\u001b[32m***** Suggested tool Call (call_cqaVQJ6VQjR7lh114bxJGVfi): create_file_with_code *****\u001b[0m\n", + "Arguments: \n", + "\n", + "{\n", + " \"filename\": \"spread_calculation.py\",\n", + " \"code\": \"import yfinance as yf\\n\\ndef calculate_daily_spread(ticker):\\n stock = yf.Ticker(ticker)\\n today_data = stock.history(period=\\\"1d\\\")\\n spread = ((today_data[\\\"High\\\"] - today_data[\\\"Low\\\"]) / today_data[\\\"Low\\\"]) * 100\\n return spread\"\n", + "}\n", + "\u001b[32m**************************************************************************************\u001b[0m\n", + "\n", + "--------------------------------------------------------------------------------\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Provide feedback to chat_manager. Press enter to skip and use auto-reply, or type 'exit' to end the conversation: \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[31m\n", + ">>>>>>>> NO HUMAN INPUT RECEIVED.\u001b[0m\n", + "\u001b[31m\n", + ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", + "\u001b[35m\n", + ">>>>>>>> EXECUTING FUNCTION create_file_with_code...\u001b[0m\n", + "\u001b[33mAdmin\u001b[0m (to chat_manager):\n", + "\n", + "\u001b[33mAdmin\u001b[0m (to chat_manager):\n", + "\n", + "\u001b[32m***** Response from calling tool \"call_cqaVQJ6VQjR7lh114bxJGVfi\" *****\u001b[0m\n", + "[0, \"File created successfully\"]\n", + "\u001b[32m**********************************************************************\u001b[0m\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mEngineer\u001b[0m (to chat_manager):\n", + "\n", + "\u001b[32m***** Suggested tool Call (call_qcoMK0RO5bXdY4yQz5RCh4mp): modify_code *****\u001b[0m\n", + "Arguments: \n", + "\n", + "{\n", + " \"filename\": \"main.py\",\n", + " \"start_line\": 1,\n", + " \"end_line\": 11,\n", + " \"new_code\": \"from fastapi import FastAPI\\nfrom spread_calculation import calculate_daily_spread\\n\\napp = FastAPI()\\n\\n@app.get(\\\"/cdr_daily_spread\\\")\\nasync def compare_daily_spread():\\n cdr_spread = calculate_daily_spread(\\\"CDR.WA\\\")\\n bits_spread = calculate_daily_spread(\\\"11B.WA\\\")\\n return cdr_spread - bits_spread\"\n", + "}\n", + "\u001b[32m****************************************************************************\u001b[0m\n", + "\n", + "--------------------------------------------------------------------------------\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Provide feedback to chat_manager. Press enter to skip and use auto-reply, or type 'exit' to end the conversation: Do not change whole the file. concentrate on the concrete lines.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mAdmin\u001b[0m (to chat_manager):\n", + "\n", + "\u001b[33mAdmin\u001b[0m (to chat_manager):\n", + "\n", + "\u001b[32m***** Response from calling tool \"call_qcoMK0RO5bXdY4yQz5RCh4mp\" *****\u001b[0m\n", + "USER INTERRUPTED\n", + "\u001b[32m**********************************************************************\u001b[0m\n", + "\n", + "--------------------------------------------------------------------------------\n", + "Do not change whole the file. concentrate on the concrete lines.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mEngineer\u001b[0m (to chat_manager):\n", + "\n", + "\u001b[32m***** Suggested tool Call (call_hz1v2vEPPxKHeygjqs2lc8ii): modify_code *****\u001b[0m\n", + "Arguments: \n", + "\n", + "{\n", + " \"filename\": \"main.py\",\n", + " \"start_line\": 1,\n", + " \"end_line\": 2,\n", + " \"new_code\": \"from fastapi import FastAPI\\nfrom spread_calculation import calculate_daily_spread\\nimport yfinance as yf\"\n", + "}\n", + "\u001b[32m****************************************************************************\u001b[0m\n", + "\n", + "--------------------------------------------------------------------------------\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Provide feedback to chat_manager. Press enter to skip and use auto-reply, or type 'exit' to end the conversation: \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[31m\n", + ">>>>>>>> NO HUMAN INPUT RECEIVED.\u001b[0m\n", + "\u001b[31m\n", + ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", + "\u001b[35m\n", + ">>>>>>>> EXECUTING FUNCTION modify_code...\u001b[0m\n", + "\u001b[33mAdmin\u001b[0m (to chat_manager):\n", + "\n", + "\u001b[33mAdmin\u001b[0m (to chat_manager):\n", + "\n", + "\u001b[32m***** Response from calling tool \"call_hz1v2vEPPxKHeygjqs2lc8ii\" *****\u001b[0m\n", + "[0, \"Code modified\"]\n", + "\u001b[32m**********************************************************************\u001b[0m\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mEngineer\u001b[0m (to chat_manager):\n", + "\n", + "\u001b[32m***** Suggested tool Call (call_1ABNdk6AATaVvn8UqLKBD5ZR): modify_code *****\u001b[0m\n", + "Arguments: \n", + "\n", + "{\n", + " \"filename\": \"main.py\",\n", + " \"start_line\": 6,\n", + " \"end_line\": 11,\n", + " \"new_code\": \"@app.get(\\\"/cdr_daily_spread\\\")\\nasync def compare_daily_spread():\\n cdr_spread = calculate_daily_spread(\\\"CDR.WA\\\")\\n bits_spread = calculate_daily_spread(\\\"11B.WA\\\")\\n return cdr_spread - bits_spread\"\n", + "}\n", + "\u001b[32m****************************************************************************\u001b[0m\n", + "\n", + "--------------------------------------------------------------------------------\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Provide feedback to chat_manager. Press enter to skip and use auto-reply, or type 'exit' to end the conversation: Check out file first to get actual lines position. Also, change endpoint name.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mAdmin\u001b[0m (to chat_manager):\n", + "\n", + "\u001b[33mAdmin\u001b[0m (to chat_manager):\n", + "\n", + "\u001b[32m***** Response from calling tool \"call_1ABNdk6AATaVvn8UqLKBD5ZR\" *****\u001b[0m\n", + "USER INTERRUPTED\n", + "\u001b[32m**********************************************************************\u001b[0m\n", + "\n", + "--------------------------------------------------------------------------------\n", + "Check out file first to get actual lines position. Also, change endpoint name.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mEngineer\u001b[0m (to chat_manager):\n", + "\n", + "\u001b[32m***** Suggested tool Call (call_2vlnu0RJ11qg6ygGT6l2tS3Q): see_file *****\u001b[0m\n", + "Arguments: \n", + "\n", + "{\n", + " \"filename\": \"main.py\"\n", + "}\n", + "\u001b[32m*************************************************************************\u001b[0m\n", + "\n", + "--------------------------------------------------------------------------------\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Provide feedback to chat_manager. Press enter to skip and use auto-reply, or type 'exit' to end the conversation: \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[31m\n", + ">>>>>>>> NO HUMAN INPUT RECEIVED.\u001b[0m\n", + "\u001b[31m\n", + ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", + "\u001b[35m\n", + ">>>>>>>> EXECUTING FUNCTION see_file...\u001b[0m\n", + "\u001b[33mAdmin\u001b[0m (to chat_manager):\n", + "\n", + "\u001b[33mAdmin\u001b[0m (to chat_manager):\n", + "\n", + "\u001b[32m***** Response from calling tool \"call_2vlnu0RJ11qg6ygGT6l2tS3Q\" *****\u001b[0m\n", + "[0, \"1:from fastapi import FastAPI\\n2:from spread_calculation import calculate_daily_spread\\n3:import yfinance as yf\\n4:\\n5:app = FastAPI()\\n6:\\n7:@app.get(\\\"/cdr_daily_spread\\\")\\n8:async def calculate_daily_spread():\\n9: cdr = yf.Ticker(\\\"CDR.WA\\\")\\n10: today_data = cdr.history(period=\\\"1d\\\")\\n11: spread = ((today_data[\\\"High\\\"] - today_data[\\\"Low\\\"]) / today_data[\\\"Low\\\"]) * 100\\n12: return spread\"]\n", + "\u001b[32m**********************************************************************\u001b[0m\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mEngineer\u001b[0m (to chat_manager):\n", + "\n", + "\u001b[32m***** Suggested tool Call (call_gNwVSM2u33Ej89Yc2IkSAovu): modify_code *****\u001b[0m\n", + "Arguments: \n", + "\n", + "{\n", + " \"filename\": \"main.py\",\n", + " \"start_line\": 7,\n", + " \"end_line\": 12,\n", + " \"new_code\": \"@app.get(\\\"/compare_daily_spread\\\")\\nasync def compare_daily_spread():\\n cdr_spread = calculate_daily_spread(\\\"CDR.WA\\\")\\n bits_spread = calculate_daily_spread(\\\"11B.WA\\\")\\n return cdr_spread - bits_spread\"\n", + "}\n", + "\u001b[32m****************************************************************************\u001b[0m\n", + "\n", + "--------------------------------------------------------------------------------\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Provide feedback to chat_manager. Press enter to skip and use auto-reply, or type 'exit' to end the conversation: \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[31m\n", + ">>>>>>>> NO HUMAN INPUT RECEIVED.\u001b[0m\n", + "\u001b[31m\n", + ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", + "\u001b[35m\n", + ">>>>>>>> EXECUTING FUNCTION modify_code...\u001b[0m\n", + "\u001b[33mAdmin\u001b[0m (to chat_manager):\n", + "\n", + "\u001b[33mAdmin\u001b[0m (to chat_manager):\n", + "\n", + "\u001b[32m***** Response from calling tool \"call_gNwVSM2u33Ej89Yc2IkSAovu\" *****\u001b[0m\n", + "[0, \"Code modified\"]\n", + "\u001b[32m**********************************************************************\u001b[0m\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mEngineer\u001b[0m (to chat_manager):\n", + "\n", + "The changes have been made successfully. Now, the FastAPI application has an endpoint `/compare_daily_spread` which calculates and returns the difference between the daily spreads of \"CDR.WA\" (CD Project Red) and \"11B.WA\" (11bits Studio). The calculation logic is now in a separate file `spread_calculation.py`.\n", + "\n", + "--------------------------------------------------------------------------------\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Provide feedback to chat_manager. Press enter to skip and use auto-reply, or type 'exit' to end the conversation: Good. But add in return some addition description to be understandable which stock change is bigger. Remember to check out main.py first to actualize your knowledge.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mAdmin\u001b[0m (to chat_manager):\n", + "\n", + "Good. But add in return some addition description to be understandable which stock change is bigger. Remember to check out main.py first to actualize your knowledge.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mEngineer\u001b[0m (to chat_manager):\n", + "\n", + "\u001b[32m***** Suggested tool Call (call_1Clo7beP49gWm1oXQXK7W6RH): see_file *****\u001b[0m\n", + "Arguments: \n", + "\n", + "{\n", + " \"filename\": \"main.py\"\n", + "}\n", + "\u001b[32m*************************************************************************\u001b[0m\n", + "\n", + "--------------------------------------------------------------------------------\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Provide feedback to chat_manager. Press enter to skip and use auto-reply, or type 'exit' to end the conversation: \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[31m\n", + ">>>>>>>> NO HUMAN INPUT RECEIVED.\u001b[0m\n", + "\u001b[31m\n", + ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", + "\u001b[35m\n", + ">>>>>>>> EXECUTING FUNCTION see_file...\u001b[0m\n", + "\u001b[33mAdmin\u001b[0m (to chat_manager):\n", + "\n", + "\u001b[33mAdmin\u001b[0m (to chat_manager):\n", + "\n", + "\u001b[32m***** Response from calling tool \"call_1Clo7beP49gWm1oXQXK7W6RH\" *****\u001b[0m\n", + "[0, \"1:from fastapi import FastAPI\\n2:from spread_calculation import calculate_daily_spread\\n3:import yfinance as yf\\n4:\\n5:app = FastAPI()\\n6:\\n7:@app.get(\\\"/compare_daily_spread\\\")\\n8:async def compare_daily_spread():\\n9: cdr_spread = calculate_daily_spread(\\\"CDR.WA\\\")\\n10: bits_spread = calculate_daily_spread(\\\"11B.WA\\\")\\n11: return cdr_spread - bits_spread\\n\"]\n", + "\u001b[32m**********************************************************************\u001b[0m\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mEngineer\u001b[0m (to chat_manager):\n", + "\n", + "\u001b[32m***** Suggested tool Call (call_Cl6jBUwvfa8JGbyldfrzYfiB): modify_code *****\u001b[0m\n", + "Arguments: \n", + "\n", + "{\n", + " \"filename\": \"main.py\",\n", + " \"start_line\": 7,\n", + " \"end_line\": 11,\n", + " \"new_code\": \"@app.get(\\\"/compare_daily_spread\\\")\\nasync def compare_daily_spread():\\n cdr_spread = calculate_daily_spread(\\\"CDR.WA\\\")\\n bits_spread = calculate_daily_spread(\\\"11B.WA\\\")\\n spread_difference = cdr_spread - bits_spread\\n if spread_difference > 0:\\n return {'message': 'CD Project Red has a larger daily spread', 'difference': spread_difference}\\n elif spread_difference < 0:\\n return {'message': '11bits Studio has a larger daily spread', 'difference': -spread_difference}\\n else:\\n return {'message': 'Both stocks have the same daily spread', 'difference': 0}\"\n", + "}\n", + "\u001b[32m****************************************************************************\u001b[0m\n", + "\n", + "--------------------------------------------------------------------------------\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Provide feedback to chat_manager. Press enter to skip and use auto-reply, or type 'exit' to end the conversation: \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[31m\n", + ">>>>>>>> NO HUMAN INPUT RECEIVED.\u001b[0m\n", + "\u001b[31m\n", + ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", + "\u001b[35m\n", + ">>>>>>>> EXECUTING FUNCTION modify_code...\u001b[0m\n", + "\u001b[33mAdmin\u001b[0m (to chat_manager):\n", + "\n", + "\u001b[33mAdmin\u001b[0m (to chat_manager):\n", + "\n", + "\u001b[32m***** Response from calling tool \"call_Cl6jBUwvfa8JGbyldfrzYfiB\" *****\u001b[0m\n", + "[0, \"Code modified\"]\n", + "\u001b[32m**********************************************************************\u001b[0m\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mEngineer\u001b[0m (to chat_manager):\n", + "\n", + "The changes have been made successfully. Now, the `/compare_daily_spread` endpoint returns a dictionary with a message indicating which stock (CD Project Red or 11bits Studio) has a larger daily spread and the absolute difference between the daily spreads. If both stocks have the same daily spread, the message will indicate this and the difference will be 0.\n", + "\n", + "--------------------------------------------------------------------------------\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Provide feedback to chat_manager. Press enter to skip and use auto-reply, or type 'exit' to end the conversation: ValueError: The truth value of a Series is ambiguous.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mAdmin\u001b[0m (to chat_manager):\n", + "\n", + "ValueError: The truth value of a Series is ambiguous.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mEngineer\u001b[0m (to chat_manager):\n", + "\n", + "The error message indicates that the comparison operation is being performed on a pandas Series object, which is ambiguous. This is likely because the `calculate_daily_spread` function is returning a Series object instead of a single value.\n", + "\n", + "To fix this, we need to ensure that the `calculate_daily_spread` function returns a single value. We can do this by taking the first (and only) value from the Series object. \n", + "\n", + "Here is the plan of changes:\n", + "\n", + "1. Modify the `calculate_daily_spread` function in `spread_calculation.py` to return only the first value from the `spread` Series.\n", + "2. No changes are required in `main.py` as the returned value from `calculate_daily_spread` function will now be a single value.\n", + "\n", + "Please confirm if this plan is correct and I can proceed with the changes.\n", + "\n", + "--------------------------------------------------------------------------------\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Provide feedback to chat_manager. Press enter to skip and use auto-reply, or type 'exit' to end the conversation: Cool. Remember to check out file before introducing modifications.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mAdmin\u001b[0m (to chat_manager):\n", + "\n", + "Cool. Remember to check out file before introducing modifications.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mEngineer\u001b[0m (to chat_manager):\n", + "\n", + "\u001b[32m***** Suggested tool Call (call_4wPhGFFW8pt8B28nVhVvWztN): see_file *****\u001b[0m\n", + "Arguments: \n", + "\n", + "{\n", + " \"filename\": \"spread_calculation.py\"\n", + "}\n", + "\u001b[32m*************************************************************************\u001b[0m\n", + "\n", + "--------------------------------------------------------------------------------\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Provide feedback to chat_manager. Press enter to skip and use auto-reply, or type 'exit' to end the conversation: \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[31m\n", + ">>>>>>>> NO HUMAN INPUT RECEIVED.\u001b[0m\n", + "\u001b[31m\n", + ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", + "\u001b[35m\n", + ">>>>>>>> EXECUTING FUNCTION see_file...\u001b[0m\n", + "\u001b[33mAdmin\u001b[0m (to chat_manager):\n", + "\n", + "\u001b[33mAdmin\u001b[0m (to chat_manager):\n", + "\n", + "\u001b[32m***** Response from calling tool \"call_4wPhGFFW8pt8B28nVhVvWztN\" *****\u001b[0m\n", + "[0, \"1:import yfinance as yf\\n2:\\n3:def calculate_daily_spread(ticker):\\n4: stock = yf.Ticker(ticker)\\n5: today_data = stock.history(period=\\\"1d\\\")\\n6: spread = ((today_data[\\\"High\\\"] - today_data[\\\"Low\\\"]) / today_data[\\\"Low\\\"]) * 100\\n7: return spread\"]\n", + "\u001b[32m**********************************************************************\u001b[0m\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mEngineer\u001b[0m (to chat_manager):\n", + "\n", + "\u001b[32m***** Suggested tool Call (call_DL0p37GzppRSmoqFaOOLSNaI): modify_code *****\u001b[0m\n", + "Arguments: \n", + "\n", + "{\n", + " \"filename\": \"spread_calculation.py\",\n", + " \"start_line\": 3,\n", + " \"end_line\": 7,\n", + " \"new_code\": \"def calculate_daily_spread(ticker):\\n stock = yf.Ticker(ticker)\\n today_data = stock.history(period=\\\"1d\\\")\\n spread = ((today_data[\\\"High\\\"] - today_data[\\\"Low\\\"]) / today_data[\\\"Low\\\"]) * 100\\n return spread.values[0]\"\n", + "}\n", + "\u001b[32m****************************************************************************\u001b[0m\n", + "\n", + "--------------------------------------------------------------------------------\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Provide feedback to chat_manager. Press enter to skip and use auto-reply, or type 'exit' to end the conversation: \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[31m\n", + ">>>>>>>> NO HUMAN INPUT RECEIVED.\u001b[0m\n", + "\u001b[31m\n", + ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", + "\u001b[35m\n", + ">>>>>>>> EXECUTING FUNCTION modify_code...\u001b[0m\n", + "\u001b[33mAdmin\u001b[0m (to chat_manager):\n", + "\n", + "\u001b[33mAdmin\u001b[0m (to chat_manager):\n", + "\n", + "\u001b[32m***** Response from calling tool \"call_DL0p37GzppRSmoqFaOOLSNaI\" *****\u001b[0m\n", + "[0, \"Code modified\"]\n", + "\u001b[32m**********************************************************************\u001b[0m\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mEngineer\u001b[0m (to chat_manager):\n", + "\n", + "The changes have been made successfully. Now, the `calculate_daily_spread` function in `spread_calculation.py` returns a single value instead of a pandas Series. This should resolve the ValueError you were encountering.\n", + "\n", + "--------------------------------------------------------------------------------\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Provide feedback to chat_manager. Press enter to skip and use auto-reply, or type 'exit' to end the conversation: exit\n" + ] + } + ], + "source": [ + "chat_result = user_proxy.initiate_chat(\n", + " manager,\n", + " message=\"\"\"\n", + "You will need to improve app in FastApi. For now, check out all the application files, try to understand it and wait for next instructions.\n", + "\"\"\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "41b6dc05-b1fc-4c1d-b101-4e91dfa63b43", + "metadata": {}, + "source": [ + "## Result\n", + "\n", + "Finally, our agents modified a code so it looks like this:" + ] + }, + { + "cell_type": "markdown", + "id": "0dec75d5-035d-4cd6-956e-cafb37f304e7", + "metadata": {}, + "source": [ + "```python\n", + "# backend_dir/main.py\n", + "\n", + "from fastapi import FastAPI\n", + "from spread_calculation import calculate_daily_spread\n", + "import yfinance as yf\n", + "\n", + "app = FastAPI()\n", + "\n", + "@app.get(\"/compare_daily_spread\")\n", + "async def compare_daily_spread():\n", + " cdr_spread = calculate_daily_spread(\"CDR.WA\")\n", + " bits_spread = calculate_daily_spread(\"11B.WA\")\n", + " spread_difference = cdr_spread - bits_spread\n", + " if spread_difference > 0:\n", + " return {'message': 'CD Project Red has a larger daily spread', 'difference': spread_difference}\n", + " elif spread_difference < 0:\n", + " return {'message': '11bits Studio has a larger daily spread', 'difference': -spread_difference}\n", + " else:\n", + " return {'message': 'Both stocks have the same daily spread', 'difference': 0}\n", + "\n", + "\n", + "# backend_dir/spread_calculation.py\n", + "\n", + "import yfinance as yf\n", + "\n", + "def calculate_daily_spread(ticker):\n", + " stock = yf.Ticker(ticker)\n", + " today_data = stock.history(period=\"1d\")\n", + " spread = ((today_data[\"High\"] - today_data[\"Low\"]) / today_data[\"Low\"]) * 100\n", + " return spread.values[0]\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "b4c1dc47-53b7-417f-af8b-f8c4d73c1d7c", + "metadata": {}, + "source": [ + "You can check out work of application with Postman or curl and see the next output:" + ] + }, + { + "cell_type": "markdown", + "id": "3d52418e-9a67-4ea2-984e-5a14bdd78255", + "metadata": {}, + "source": [ + "```json\n", + "{\n", + " \"message\": \"11bits Studio has a larger daily spread\",\n", + " \"difference\": 1.7968083865943187\n", + "}\n", + "```" + ] + } + ], + "metadata": { + "front_matter": { + "description": "Equip your agent with functions that can efficiently implement features into your software application.", + "tags": [ + "function call", + "code generation", + "tool use", + "software engineering" + ] + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebook/agentchat_function_call_currency_calculator.ipynb b/notebook/agentchat_function_call_currency_calculator.ipynb index 6709aec7762..a7a5a92bbd9 100644 --- a/notebook/agentchat_function_call_currency_calculator.ipynb +++ b/notebook/agentchat_function_call_currency_calculator.ipynb @@ -6,7 +6,7 @@ "id": "ae1f50ec", "metadata": {}, "source": [ - "\"Open" + "# Currency Calculator: Task Solving with Provided Tools as Functions\n" ] }, { @@ -15,8 +15,6 @@ "id": "9a71fa36", "metadata": {}, "source": [ - "# Currency Calculator: Task Solving with Provided Tools as Functions\n", - "\n", "AutoGen offers conversable agents powered by LLM, tool, or human, which can be used to perform tasks collectively via automated chat. This framework allows tool use and human participation through multi-agent conversation. Please find documentation about this feature [here](https://microsoft.github.io/autogen/docs/Use-Cases/agent_chat).\n", "\n", "In this notebook, we demonstrate how to use `AssistantAgent` and `UserProxyAgent` to make function calls with the new feature of OpenAI models (in model version 0613). A specified prompt and function configs must be passed to `AssistantAgent` to initialize the agent. The corresponding functions must be passed to `UserProxyAgent`, which will execute any function calls made by `AssistantAgent`. Besides this requirement of matching descriptions with functions, we recommend checking the system message in the `AssistantAgent` to ensure the instructions align with the function call descriptions.\n", @@ -31,7 +29,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 14, "id": "2b803c17", "metadata": {}, "outputs": [], @@ -52,7 +50,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 15, "id": "dca301a4", "metadata": {}, "outputs": [], @@ -65,12 +63,9 @@ "import autogen\n", "from autogen.cache import Cache\n", "\n", - "\n", "config_list = autogen.config_list_from_json(\n", " \"OAI_CONFIG_LIST\",\n", - " filter_dict={\n", - " \"model\": [\"gpt-4\", \"gpt-3.5-turbo\", \"gpt-3.5-turbo-16k\"],\n", - " },\n", + " filter_dict={\"tags\": [\"3.5-tool\"]}, # comment out to get all\n", ")" ] }, @@ -80,14 +75,15 @@ "id": "92fde41f", "metadata": {}, "source": [ - "It first looks for environment variable \"OAI_CONFIG_LIST\" which needs to be a valid json string. If that variable is not found, it then looks for a json file named \"OAI_CONFIG_LIST\". It filters the configs by models (you can filter by other keys as well). Only the models with matching names are kept in the list based on the filter condition.\n", + "It first looks for environment variable \"OAI_CONFIG_LIST\" which needs to be a valid json string. If that variable is not found, it then looks for a json file named \"OAI_CONFIG_LIST\". It filters the configs by tags (you can filter by other keys as well). Only the configs with matching tags are kept in the list based on the filter condition.\n", "\n", "The config list looks like the following:\n", "```python\n", "config_list = [\n", " {\n", - " 'model': 'gpt-4',\n", + " 'model': 'gpt-3.5-turbo',\n", " 'api_key': '',\n", + " 'tags': ['tool', '3.5-tool'],\n", " },\n", " {\n", " 'model': 'gpt-3.5-turbo',\n", @@ -95,6 +91,7 @@ " 'base_url': '',\n", " 'api_type': 'azure',\n", " 'api_version': '2024-02-15-preview',\n", + " 'tags': ['tool', '3.5-tool'],\n", " },\n", " {\n", " 'model': 'gpt-3.5-turbo-16k',\n", @@ -102,6 +99,7 @@ " 'base_url': '',\n", " 'api_type': 'azure',\n", " 'api_version': '2024-02-15-preview',\n", + " 'tags': ['tool', '3.5-tool'],\n", " },\n", "]\n", "```\n", @@ -122,7 +120,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 16, "id": "9fb85afb", "metadata": {}, "outputs": [], @@ -182,7 +180,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 17, "id": "3e52bbfe", "metadata": {}, "outputs": [ @@ -206,7 +204,7 @@ " 'required': ['base_amount']}}}]" ] }, - "execution_count": 4, + "execution_count": 17, "metadata": {}, "output_type": "execute_result" } @@ -231,7 +229,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 18, "id": "bd943369", "metadata": {}, "outputs": [], @@ -249,7 +247,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 19, "id": "d5518947", "metadata": {}, "outputs": [ @@ -261,12 +259,22 @@ "\n", "How much is 123.45 USD in EUR?\n", "\n", - "--------------------------------------------------------------------------------\n", + "--------------------------------------------------------------------------------\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ "\u001b[33mchatbot\u001b[0m (to user_proxy):\n", "\n", - "\u001b[32m***** Suggested tool Call (call_Ak49uR4cwLWyPKs5T2gK9bMg): currency_calculator *****\u001b[0m\n", + "\u001b[32m***** Suggested tool call (call_9ogJS4d40BT1rXfMn7YJb151): currency_calculator *****\u001b[0m\n", "Arguments: \n", - "{\"base_amount\":123.45}\n", + "{\n", + " \"base_amount\": 123.45,\n", + " \"base_currency\": \"USD\",\n", + " \"quote_currency\": \"EUR\"\n", + "}\n", "\u001b[32m************************************************************************************\u001b[0m\n", "\n", "--------------------------------------------------------------------------------\n", @@ -276,14 +284,14 @@ "\n", "\u001b[33muser_proxy\u001b[0m (to chatbot):\n", "\n", - "\u001b[32m***** Response from calling tool \"call_Ak49uR4cwLWyPKs5T2gK9bMg\" *****\u001b[0m\n", + "\u001b[32m***** Response from calling tool (call_9ogJS4d40BT1rXfMn7YJb151) *****\u001b[0m\n", "112.22727272727272 EUR\n", "\u001b[32m**********************************************************************\u001b[0m\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mchatbot\u001b[0m (to user_proxy):\n", "\n", - "123.45 USD is approximately 112.23 EUR.\n", + "123.45 USD is equivalent to 112.23 EUR.\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33muser_proxy\u001b[0m (to chatbot):\n", @@ -309,7 +317,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 20, "id": "4b5a0edc", "metadata": {}, "outputs": [ @@ -317,7 +325,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Chat summary: 123.45 USD is equivalent to approximately 112.23 EUR.\n" + "Chat summary: 123.45 USD is equivalent to 112.23 EUR.\n" ] } ], @@ -343,7 +351,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 21, "id": "7b3d8b58", "metadata": {}, "outputs": [], @@ -392,7 +400,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 22, "id": "971ed0d5", "metadata": {}, "outputs": [ @@ -423,7 +431,7 @@ " 'required': ['base']}}}]" ] }, - "execution_count": 8, + "execution_count": 22, "metadata": {}, "output_type": "execute_result" } @@ -434,7 +442,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 23, "id": "ab081090", "metadata": {}, "outputs": [ @@ -446,12 +454,24 @@ "\n", "How much is 112.23 Euros in US Dollars?\n", "\n", - "--------------------------------------------------------------------------------\n", + "--------------------------------------------------------------------------------\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ "\u001b[33mchatbot\u001b[0m (to user_proxy):\n", "\n", - "\u001b[32m***** Suggested tool Call (call_G64JQKQBT2rI4vnuA4iz1vmE): currency_calculator *****\u001b[0m\n", + "\u001b[32m***** Suggested tool call (call_BQkSmdFHsrKvmtDWCk0mY5sF): currency_calculator *****\u001b[0m\n", "Arguments: \n", - "{\"base\":{\"currency\":\"EUR\",\"amount\":112.23},\"quote_currency\":\"USD\"}\n", + "{\n", + " \"base\": {\n", + " \"currency\": \"EUR\",\n", + " \"amount\": 112.23\n", + " },\n", + " \"quote_currency\": \"USD\"\n", + "}\n", "\u001b[32m************************************************************************************\u001b[0m\n", "\n", "--------------------------------------------------------------------------------\n", @@ -461,23 +481,14 @@ "\n", "\u001b[33muser_proxy\u001b[0m (to chatbot):\n", "\n", - "\u001b[32m***** Response from calling tool \"call_G64JQKQBT2rI4vnuA4iz1vmE\" *****\u001b[0m\n", + "\u001b[32m***** Response from calling tool (call_BQkSmdFHsrKvmtDWCk0mY5sF) *****\u001b[0m\n", "{\"currency\":\"USD\",\"amount\":123.45300000000002}\n", "\u001b[32m**********************************************************************\u001b[0m\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mchatbot\u001b[0m (to user_proxy):\n", "\n", - "112.23 Euros is equivalent to approximately 123.45 US Dollars.\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33muser_proxy\u001b[0m (to chatbot):\n", - "\n", - "\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mchatbot\u001b[0m (to user_proxy):\n", - "\n", + "112.23 Euros is equivalent to 123.45 US Dollars.\n", "TERMINATE\n", "\n", "--------------------------------------------------------------------------------\n" @@ -494,7 +505,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 24, "id": "4799f60c", "metadata": {}, "outputs": [ @@ -502,7 +513,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Chat summary: 112.23 Euros is approximately 123.45 US Dollars.\n" + "Chat summary: 112.23 Euros is equivalent to 123.45 US Dollars.\n" ] } ], @@ -512,7 +523,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 25, "id": "0064d9cd", "metadata": {}, "outputs": [ @@ -524,12 +535,24 @@ "\n", "How much is 123.45 US Dollars in Euros?\n", "\n", - "--------------------------------------------------------------------------------\n", + "--------------------------------------------------------------------------------\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ "\u001b[33mchatbot\u001b[0m (to user_proxy):\n", "\n", - "\u001b[32m***** Suggested tool Call (call_qv2SwJHpKrG73btxNzUnYBoR): currency_calculator *****\u001b[0m\n", + "\u001b[32m***** Suggested tool call (call_Xxol42xTswZHGX60OjvIQRG1): currency_calculator *****\u001b[0m\n", "Arguments: \n", - "{\"base\":{\"currency\":\"USD\",\"amount\":123.45},\"quote_currency\":\"EUR\"}\n", + "{\n", + " \"base\": {\n", + " \"currency\": \"USD\",\n", + " \"amount\": 123.45\n", + " },\n", + " \"quote_currency\": \"EUR\"\n", + "}\n", "\u001b[32m************************************************************************************\u001b[0m\n", "\n", "--------------------------------------------------------------------------------\n", @@ -539,14 +562,14 @@ "\n", "\u001b[33muser_proxy\u001b[0m (to chatbot):\n", "\n", - "\u001b[32m***** Response from calling tool \"call_qv2SwJHpKrG73btxNzUnYBoR\" *****\u001b[0m\n", + "\u001b[32m***** Response from calling tool (call_Xxol42xTswZHGX60OjvIQRG1) *****\u001b[0m\n", "{\"currency\":\"EUR\",\"amount\":112.22727272727272}\n", "\u001b[32m**********************************************************************\u001b[0m\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mchatbot\u001b[0m (to user_proxy):\n", "\n", - "123.45 US Dollars is approximately 112.23 Euros.\n", + "123.45 US Dollars is equivalent to 112.23 Euros.\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33muser_proxy\u001b[0m (to chatbot):\n", @@ -574,7 +597,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 26, "id": "80b2b42c", "metadata": {}, "outputs": [ @@ -582,7 +605,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Chat history: [{'content': 'How much is 123.45 US Dollars in Euros?', 'role': 'assistant'}, {'tool_calls': [{'id': 'call_qv2SwJHpKrG73btxNzUnYBoR', 'function': {'arguments': '{\"base\":{\"currency\":\"USD\",\"amount\":123.45},\"quote_currency\":\"EUR\"}', 'name': 'currency_calculator'}, 'type': 'function'}], 'content': None, 'role': 'assistant'}, {'content': '{\"currency\":\"EUR\",\"amount\":112.22727272727272}', 'tool_responses': [{'tool_call_id': 'call_qv2SwJHpKrG73btxNzUnYBoR', 'role': 'tool', 'content': '{\"currency\":\"EUR\",\"amount\":112.22727272727272}'}], 'role': 'tool'}, {'content': '123.45 US Dollars is approximately 112.23 Euros.', 'role': 'user'}, {'content': '', 'role': 'assistant'}, {'content': 'TERMINATE', 'role': 'user'}]\n" + "Chat history: [{'content': 'How much is 123.45 US Dollars in Euros?', 'role': 'assistant'}, {'tool_calls': [{'id': 'call_Xxol42xTswZHGX60OjvIQRG1', 'function': {'arguments': '{\\n \"base\": {\\n \"currency\": \"USD\",\\n \"amount\": 123.45\\n },\\n \"quote_currency\": \"EUR\"\\n}', 'name': 'currency_calculator'}, 'type': 'function'}], 'content': None, 'role': 'assistant'}, {'content': '{\"currency\":\"EUR\",\"amount\":112.22727272727272}', 'tool_responses': [{'tool_call_id': 'call_Xxol42xTswZHGX60OjvIQRG1', 'role': 'tool', 'content': '{\"currency\":\"EUR\",\"amount\":112.22727272727272}'}], 'role': 'tool'}, {'content': '123.45 US Dollars is equivalent to 112.23 Euros.', 'role': 'user'}, {'content': '', 'role': 'assistant'}, {'content': 'TERMINATE', 'role': 'user'}]\n" ] } ], @@ -592,6 +615,13 @@ } ], "metadata": { + "front_matter": { + "description": "Learn how to register function calls using AssistantAgent and UserProxyAgent in AutoGen.", + "tags": [ + "function call", + "tool use" + ] + }, "kernelspec": { "display_name": "flaml_dev", "language": "python", diff --git a/notebook/agentchat_groupchat_RAG.ipynb b/notebook/agentchat_groupchat_RAG.ipynb index 27b5d2abbe2..1057deabf92 100644 --- a/notebook/agentchat_groupchat_RAG.ipynb +++ b/notebook/agentchat_groupchat_RAG.ipynb @@ -42,13 +42,13 @@ "name": "stdout", "output_type": "stream", "text": [ - "LLM models: ['gpt-4-1106-preview', 'gpt-4-turbo-preview', 'gpt-4-0613', 'gpt-35-turbo-0613', 'gpt-35-turbo-1106']\n" + "LLM models: ['gpt4-1106-preview', 'gpt-35-turbo', 'gpt-35-turbo-0613']\n" ] } ], "source": [ - "from typing_extensions import Annotated\n", "import chromadb\n", + "from typing_extensions import Annotated\n", "\n", "import autogen\n", "from autogen import AssistantAgent\n", @@ -77,19 +77,23 @@ "cell_type": "code", "execution_count": 2, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages/transformers/utils/generic.py:311: UserWarning: torch.utils._pytree._register_pytree_node is deprecated. Please use torch.utils._pytree.register_pytree_node instead.\n", + " torch.utils._pytree._register_pytree_node(\n" + ] + } + ], "source": [ - "llm_config = {\n", - " \"timeout\": 60,\n", - " \"temperature\": 0,\n", - " \"config_list\": config_list,\n", - "}\n", - "\n", - "\n", "def termination_msg(x):\n", " return isinstance(x, dict) and \"TERMINATE\" == str(x.get(\"content\", \"\"))[-9:].upper()\n", "\n", "\n", + "llm_config = {\"config_list\": config_list, \"timeout\": 60, \"temperature\": 0.8, \"seed\": 1234}\n", + "\n", "boss = autogen.UserProxyAgent(\n", " name=\"Boss\",\n", " is_termination_msg=termination_msg,\n", @@ -103,13 +107,13 @@ " name=\"Boss_Assistant\",\n", " is_termination_msg=termination_msg,\n", " human_input_mode=\"NEVER\",\n", + " default_auto_reply=\"Reply `TERMINATE` if the task is done.\",\n", " max_consecutive_auto_reply=3,\n", " retrieve_config={\n", " \"task\": \"code\",\n", " \"docs_path\": \"https://raw.githubusercontent.com/microsoft/FLAML/main/website/docs/Examples/Integrate%20-%20Spark.md\",\n", " \"chunk_token_size\": 1000,\n", " \"model\": config_list[0][\"model\"],\n", - " \"client\": chromadb.PersistentClient(path=\"/tmp/chromadb\"),\n", " \"collection_name\": \"groupchat\",\n", " \"get_or_create\": True,\n", " },\n", @@ -271,49 +275,129 @@ "text": [ "\u001b[33mSenior_Python_Engineer\u001b[0m (to chat_manager):\n", "\n", - "To use Apache Spark for parallel training in FLAML, you need to use the `flaml.tune.run` function. Here is a sample code:\n", + "To use Spark for parallel training in FLAML (Fast and Lightweight AutoML), you would need to set up a Spark cluster and utilize the `spark` backend for joblib, which FLAML uses internally for parallel training. Here’s an example of how you might set up and use Spark with FLAML for AutoML tasks:\n", + "\n", + "Firstly, ensure that you have the Spark cluster set up and the `pyspark` and `joblib-spark` packages installed in your environment. You can install the required packages using pip if they are not already installed:\n", "\n", "```python\n", - "from flaml import tune\n", + "!pip install flaml pyspark joblib-spark\n", + "```\n", + "\n", + "Here's a sample code snippet that demonstrates how to use FLAML with Spark for parallel training:\n", + "\n", + "```python\n", + "from flaml import AutoML\n", + "from pyspark.sql import SparkSession\n", + "from sklearn.datasets import load_digits\n", + "from joblibspark import register_spark\n", + "\n", + "# Initialize a Spark session\n", + "spark = SparkSession.builder \\\n", + " .master(\"local[*]\") \\\n", + " .appName(\"FLAML_Spark_Example\") \\\n", + " .getOrCreate()\n", + "\n", + "# Register the joblib spark backend\n", + "register_spark() # This registers the backend for parallel processing\n", "\n", - "# Define your training function\n", - "def training_function(config):\n", - " # your training code here\n", - " pass\n", + "# Load sample data\n", + "X, y = load_digits(return_X_y=True)\n", "\n", - "# Define your search space\n", - "search_space = {\n", - " \"lr\": tune.loguniform(1e-4, 1e-1),\n", - " \"momentum\": tune.uniform(0.1, 0.9),\n", + "# Initialize an AutoML instance\n", + "automl = AutoML()\n", + "\n", + "# Define the settings for the AutoML run\n", + "settings = {\n", + " \"time_budget\": 60, # Total running time in seconds\n", + " \"metric\": 'accuracy', # Primary metric for evaluation\n", + " \"task\": 'classification', # Task type\n", + " \"n_jobs\": -1, # Number of jobs to run in parallel (use -1 for all)\n", + " \"estimator_list\": ['lgbm', 'rf', 'xgboost'], # List of estimators to consider\n", + " \"log_file_name\": \"flaml_log.txt\", # Log file name\n", "}\n", "\n", - "# Use SparkTrials for parallelization\n", - "from ray.tune import SparkTrials\n", + "# Run the AutoML search with Spark backend\n", + "automl.fit(X_train=X, y_train=y, **settings)\n", "\n", - "spark_trials = SparkTrials(parallelism=2)\n", + "# Output the best model and its performance\n", + "print(f\"Best ML model: {automl.model}\")\n", + "print(f\"Best ML model's accuracy: {automl.best_loss}\")\n", "\n", - "analysis = tune.run(\n", - " training_function,\n", - " config=search_space,\n", - " num_samples=10,\n", - " scheduler=tune.schedulers.FIFOScheduler(),\n", - " progress_reporter=tune.JupyterNotebookReporter(overwrite=True),\n", - " trial_executor=spark_trials,\n", - ")\n", + "# Stop the Spark session\n", + "spark.stop()\n", + "```\n", + "\n", + "The `register_spark()` function from `joblib-spark` is used to register the Spark backend with joblib, which is utilized for parallel training within FLAML. The `n_jobs=-1` parameter tells FLAML to use all available Spark executors for parallel training.\n", + "\n", + "Please note that the actual process of setting up a Spark cluster can be complex and might involve additional steps such as configuring Spark workers, allocating resources, and more, which are beyond the scope of this code snippet.\n", "\n", - "print(\"Best config: \", analysis.get_best_config(metric=\"accuracy\", mode=\"max\"))\n", + "If you encounter any issues or need to adjust configurations for your specific Spark setup, please refer to the Spark and FLAML documentation for more details.\n", "\n", - "# Get a dataframe for analyzing trial results.\n", - "df = analysis.results_df\n", + "When you run the code, ensure that your Spark cluster is properly configured and accessible from your Python environment. Adjust the `.master(\"local[*]\")` to point to your Spark master's URL if you are running a cluster that is not local.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "To use Spark for parallel training in FLAML (Fast and Lightweight AutoML), you would need to set up a Spark cluster and utilize the `spark` backend for joblib, which FLAML uses internally for parallel training. Here’s an example of how you might set up and use Spark with FLAML for AutoML tasks:\n", + "\n", + "Firstly, ensure that you have the Spark cluster set up and the `pyspark` and `joblib-spark` packages installed in your environment. You can install the required packages using pip if they are not already installed:\n", + "\n", + "```python\n", + "!pip install flaml pyspark joblib-spark\n", "```\n", "\n", - "In this code, `training_function` is your training function, which should take a `config` argument. This `config` argument is a dictionary that includes hyperparameters for your model. The `search_space` is a dictionary that defines the search space for your hyperparameters.\n", + "Here's a sample code snippet that demonstrates how to use FLAML with Spark for parallel training:\n", + "\n", + "```python\n", + "from flaml import AutoML\n", + "from pyspark.sql import SparkSession\n", + "from sklearn.datasets import load_digits\n", + "from joblibspark import register_spark\n", + "\n", + "# Initialize a Spark session\n", + "spark = SparkSession.builder \\\n", + " .master(\"local[*]\") \\\n", + " .appName(\"FLAML_Spark_Example\") \\\n", + " .getOrCreate()\n", + "\n", + "# Register the joblib spark backend\n", + "register_spark() # This registers the backend for parallel processing\n", "\n", - "The `tune.run` function is used to start the hyperparameter tuning. The `config` argument is your search space, `num_samples` is the number of times to sample from the search space, and `scheduler` is the scheduler for the trials. The `trial_executor` argument is set to `spark_trials` to use Spark for parallelization.\n", + "# Load sample data\n", + "X, y = load_digits(return_X_y=True)\n", "\n", - "The `analysis.get_best_config` function is used to get the best hyperparameters found during the tuning. The `analysis.results_df` gives a dataframe that contains the results of all trials.\n", + "# Initialize an AutoML instance\n", + "automl = AutoML()\n", "\n", - "Please note that you need to have Apache Spark and Ray installed and properly configured in your environment to run this code.\n", + "# Define the settings for the AutoML run\n", + "settings = {\n", + " \"time_budget\": 60, # Total running time in seconds\n", + " \"metric\": 'accuracy', # Primary metric for evaluation\n", + " \"task\": 'classification', # Task type\n", + " \"n_jobs\": -1, # Number of jobs to run in parallel (use -1 for all)\n", + " \"estimator_list\": ['lgbm', 'rf', 'xgboost'], # List of estimators to consider\n", + " \"log_file_name\": \"flaml_log.txt\", # Log file name\n", + "}\n", + "\n", + "# Run the AutoML search with Spark backend\n", + "automl.fit(X_train=X, y_train=y, **settings)\n", + "\n", + "# Output the best model and its performance\n", + "print(f\"Best ML model: {automl.model}\")\n", + "print(f\"Best ML model's accuracy: {automl.best_loss}\")\n", + "\n", + "# Stop the Spark session\n", + "spark.stop()\n", + "```\n", + "\n", + "The `register_spark()` function from `joblib-spark` is used to register the Spark backend with joblib, which is utilized for parallel training within FLAML. The `n_jobs=-1` parameter tells FLAML to use all available Spark executors for parallel training.\n", + "\n", + "Please note that the actual process of setting up a Spark cluster can be complex and might involve additional steps such as configuring Spark workers, allocating resources, and more, which are beyond the scope of this code snippet.\n", + "\n", + "If you encounter any issues or need to adjust configurations for your specific Spark setup, please refer to the Spark and FLAML documentation for more details.\n", + "\n", + "When you run the code, ensure that your Spark cluster is properly configured and accessible from your Python environment. Adjust the `.master(\"local[*]\")` to point to your Spark master's URL if you are running a cluster that is not local.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mCode_Reviewer\u001b[0m (to chat_manager):\n", "\n", "TERMINATE\n", "\n", @@ -336,17 +420,38 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 4, "metadata": {}, "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2024-04-07 18:26:04,562 - autogen.agentchat.contrib.retrieve_user_proxy_agent - INFO - \u001b[32mUse the existing collection `groupchat`.\u001b[0m\n" + ] + }, { "name": "stdout", "output_type": "stream", "text": [ - "doc_ids: [['doc_0', 'doc_1', 'doc_122']]\n", - "\u001b[32mAdding doc_id doc_0 to context.\u001b[0m\n", - "\u001b[32mAdding doc_id doc_1 to context.\u001b[0m\n", - "\u001b[32mAdding doc_id doc_122 to context.\u001b[0m\n", + "Trying to create collection.\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2024-04-07 18:26:05,485 - autogen.agentchat.contrib.retrieve_user_proxy_agent - INFO - Found 1 chunks.\u001b[0m\n", + "Number of requested results 3 is greater than number of elements in index 1, updating n_results = 1\n", + "Model gpt4-1106-preview not found. Using cl100k_base encoding.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "VectorDB returns doc_ids: [['bdfbc921']]\n", + "\u001b[32mAdding content of doc bdfbc921 to context.\u001b[0m\n", "\u001b[33mBoss_Assistant\u001b[0m (to chat_manager):\n", "\n", "You're a retrieve augmented coding assistant. You answer user's questions based on your own knowledge and the\n", @@ -364,6 +469,7 @@ "Context is: # Integrate - Spark\n", "\n", "FLAML has integrated Spark for distributed training. There are two main aspects of integration with Spark:\n", + "\n", "- Use Spark ML estimators for AutoML.\n", "- Use Spark to run training in parallel spark jobs.\n", "\n", @@ -378,6 +484,7 @@ "This utility function takes data in the form of a `pandas.Dataframe` or `pyspark.sql.Dataframe` and converts it into a pandas-on-spark dataframe. It also takes `pandas.Series` or `pyspark.sql.Dataframe` and converts it into a [pandas-on-spark](https://spark.apache.org/docs/latest/api/python/user_guide/pandas_on_spark/index.html) series. If you pass in a `pyspark.pandas.Dataframe`, it will not make any changes.\n", "\n", "This function also accepts optional arguments `index_col` and `default_index_type`.\n", + "\n", "- `index_col` is the column name to use as the index, default is None.\n", "- `default_index_type` is the default index type, default is \"distributed-sequence\". More info about default index type could be found on Spark official [documentation](https://spark.apache.org/docs/latest/api/python/user_guide/pandas_on_spark/options.html#default-index-type)\n", "\n", @@ -386,10 +493,13 @@ "```python\n", "import pandas as pd\n", "from flaml.automl.spark.utils import to_pandas_on_spark\n", + "\n", "# Creating a dictionary\n", - "data = {\"Square_Feet\": [800, 1200, 1800, 1500, 850],\n", - " \"Age_Years\": [20, 15, 10, 7, 25],\n", - " \"Price\": [100000, 200000, 300000, 240000, 120000]}\n", + "data = {\n", + " \"Square_Feet\": [800, 1200, 1800, 1500, 850],\n", + " \"Age_Years\": [20, 15, 10, 7, 25],\n", + " \"Price\": [100000, 200000, 300000, 240000, 120000],\n", + "}\n", "\n", "# Creating a pandas DataFrame\n", "dataframe = pd.DataFrame(data)\n", @@ -402,8 +512,10 @@ "To use Spark ML models you need to format your data appropriately. Specifically, use [`VectorAssembler`](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.ml.feature.VectorAssembler.html) to merge all feature columns into a single vector column.\n", "\n", "Here is an example of how to use it:\n", + "\n", "```python\n", "from pyspark.ml.feature import VectorAssembler\n", + "\n", "columns = psdf.columns\n", "feature_cols = [col for col in columns if col != label]\n", "featurizer = VectorAssembler(inputCols=feature_cols, outputCol=\"features\")\n", @@ -413,10 +525,13 @@ "Later in conducting the experiment, use your pandas-on-spark data like non-spark data and pass them using `X_train, y_train` or `dataframe, label`.\n", "\n", "### Estimators\n", + "\n", "#### Model List\n", + "\n", "- `lgbm_spark`: The class for fine-tuning Spark version LightGBM models, using [SynapseML](https://microsoft.github.io/SynapseML/docs/features/lightgbm/about/) API.\n", "\n", "#### Usage\n", + "\n", "First, prepare your data in the required format as described in the previous section.\n", "\n", "By including the models you intend to try in the `estimators_list` argument to `flaml.automl`, FLAML will start trying configurations for these models. If your input is Spark data, FLAML will also use estimators with the `_spark` postfix by default, even if you haven't specified them.\n", @@ -425,6 +540,7 @@ "\n", "```python\n", "import flaml\n", + "\n", "# prepare your data in pandas-on-spark format as we previously mentioned\n", "\n", "automl = flaml.AutoML()\n", @@ -442,24 +558,25 @@ ")\n", "```\n", "\n", - "\n", "[Link to notebook](https://github.com/microsoft/FLAML/blob/main/notebook/automl_bankrupt_synapseml.ipynb) | [Open in colab](https://colab.research.google.com/github/microsoft/FLAML/blob/main/notebook/automl_bankrupt_synapseml.ipynb)\n", "\n", "## Parallel Spark Jobs\n", + "\n", "You can activate Spark as the parallel backend during parallel tuning in both [AutoML](/docs/Use-Cases/Task-Oriented-AutoML#parallel-tuning) and [Hyperparameter Tuning](/docs/Use-Cases/Tune-User-Defined-Function#parallel-tuning), by setting the `use_spark` to `true`. FLAML will dispatch your job to the distributed Spark backend using [`joblib-spark`](https://github.com/joblib/joblib-spark).\n", "\n", "Please note that you should not set `use_spark` to `true` when applying AutoML and Tuning for Spark Data. This is because only SparkML models will be used for Spark Data in AutoML and Tuning. As SparkML models run in parallel, there is no need to distribute them with `use_spark` again.\n", "\n", "All the Spark-related arguments are stated below. These arguments are available in both Hyperparameter Tuning and AutoML:\n", "\n", - "\n", "- `use_spark`: boolean, default=False | Whether to use spark to run the training in parallel spark jobs. This can be used to accelerate training on large models and large datasets, but will incur more overhead in time and thus slow down training in some cases. GPU training is not supported yet when use_spark is True. For Spark clusters, by default, we will launch one trial per executor. However, sometimes we want to launch more trials than the number of executors (e.g., local mode). In this case, we can set the environment variable `FLAML_MAX_CONCURRENT` to override the detected `num_executors`. The final number of concurrent trials will be the minimum of `n_concurrent_trials` and `num_executors`.\n", "- `n_concurrent_trials`: int, default=1 | The number of concurrent trials. When n_concurrent_trials > 1, FLAML performes parallel tuning.\n", "- `force_cancel`: boolean, default=False | Whether to forcely cancel Spark jobs if the search time exceeded the time budget. Spark jobs include parallel tuning jobs and Spark-based model training jobs.\n", "\n", "An example code snippet for using parallel Spark jobs:\n", + "\n", "```python\n", "import flaml\n", + "\n", "automl_experiment = flaml.AutoML()\n", "automl_settings = {\n", " \"time_budget\": 30,\n", @@ -467,7 +584,7 @@ " \"task\": \"regression\",\n", " \"n_concurrent_trials\": 2,\n", " \"use_spark\": True,\n", - " \"force_cancel\": True, # Activating the force_cancel option can immediately halt Spark jobs once they exceed the allocated time_budget.\n", + " \"force_cancel\": True, # Activating the force_cancel option can immediately halt Spark jobs once they exceed the allocated time_budget.\n", "}\n", "\n", "automl.fit(\n", @@ -477,127 +594,48 @@ ")\n", "```\n", "\n", - "\n", "[Link to notebook](https://github.com/microsoft/FLAML/blob/main/notebook/integrate_spark.ipynb) | [Open in colab](https://colab.research.google.com/github/microsoft/FLAML/blob/main/notebook/integrate_spark.ipynb)\n", "\n", - "2684,4/26/2011,2,0,4,17,0,2,1,1,0.68,0.6364,0.61,0.3582,521\n", - "2685,4/26/2011,2,0,4,18,0,2,1,1,0.68,0.6364,0.65,0.4478,528\n", - "2686,4/26/2011,2,0,4,19,0,2,1,1,0.64,0.6061,0.73,0.4179,328\n", - "2687,4/26/2011,2,0,4,20,0,2,1,1,0.64,0.6061,0.73,0.3582,234\n", - "2688,4/26/2011,2,0,4,21,0,2,1,1,0.62,0.5909,0.78,0.2836,195\n", - "2689,4/26/2011,2,0,4,22,0,2,1,2,0.6,0.5606,0.83,0.194,148\n", - "2690,4/26/2011,2,0,4,23,0,2,1,2,0.6,0.5606,0.83,0.2239,78\n", - "2691,4/27/2011,2,0,4,0,0,3,1,1,0.6,0.5606,0.83,0.2239,27\n", - "2692,4/27/2011,2,0,4,1,0,3,1,1,0.6,0.5606,0.83,0.2537,17\n", - "2693,4/27/2011,2,0,4,2,0,3,1,1,0.58,0.5455,0.88,0.2537,5\n", - "2694,4/27/2011,2,0,4,3,0,3,1,2,0.58,0.5455,0.88,0.2836,7\n", - "2695,4/27/2011,2,0,4,4,0,3,1,1,0.56,0.5303,0.94,0.2239,6\n", - "2696,4/27/2011,2,0,4,5,0,3,1,2,0.56,0.5303,0.94,0.2537,17\n", - "2697,4/27/2011,2,0,4,6,0,3,1,1,0.56,0.5303,0.94,0.2537,84\n", - "2698,4/27/2011,2,0,4,7,0,3,1,2,0.58,0.5455,0.88,0.2836,246\n", - "2699,4/27/2011,2,0,4,8,0,3,1,2,0.58,0.5455,0.88,0.3284,444\n", - "2700,4/27/2011,2,0,4,9,0,3,1,2,0.6,0.5455,0.88,0.4179,181\n", - "2701,4/27/2011,2,0,4,10,0,3,1,2,0.62,0.5758,0.83,0.2836,92\n", - "2702,4/27/2011,2,0,4,11,0,3,1,2,0.64,0.5909,0.78,0.2836,156\n", - "2703,4/27/2011,2,0,4,12,0,3,1,1,0.66,0.6061,0.78,0.3284,173\n", - "2704,4/27/2011,2,0,4,13,0,3,1,1,0.64,0.5909,0.78,0.2985,150\n", - "2705,4/27/2011,2,0,4,14,0,3,1,1,0.68,0.6364,0.74,0.2836,148\n", - "\n", - "\n", - "\n", - "--------------------------------------------------------------------------------\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[33mProduct_Manager\u001b[0m (to chat_manager):\n", "\n", - "To use Spark for parallel training in FLAML, you can follow these steps:\n", "\n", - "1. Prepare your data in the required format using the `to_pandas_on_spark` function from the `flaml.automl.spark.utils` module. This function converts your data into a pandas-on-spark dataframe, which is required by Spark estimators. Here is an example code snippet:\n", - "\n", - "```python\n", - "import pandas as pd\n", - "from flaml.automl.spark.utils import to_pandas_on_spark\n", - "\n", - "# Creating a dictionary\n", - "data = {\n", - " \"Square_Feet\": [800, 1200, 1800, 1500, 850],\n", - " \"Age_Years\": [20, 15, 10, 7, 25],\n", - " \"Price\": [100000, 200000, 300000, 240000, 120000]\n", - "}\n", - "\n", - "# Creating a pandas DataFrame\n", - "dataframe = pd.DataFrame(data)\n", - "label = \"Price\"\n", - "\n", - "# Convert to pandas-on-spark dataframe\n", - "psdf = to_pandas_on_spark(dataframe)\n", - "```\n", - "\n", - "2. Format your data appropriately for Spark ML models. Use the `VectorAssembler` from `pyspark.ml.feature` to merge all feature columns into a single vector column. Here is an example:\n", - "\n", - "```python\n", - "from pyspark.ml.feature import VectorAssembler\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mBoss_Assistant\u001b[0m (to chat_manager):\n", "\n", - "columns = psdf.columns\n", - "feature_cols = [col for col in columns if col != label]\n", - "featurizer = VectorAssembler(inputCols=feature_cols, outputCol=\"features\")\n", - "psdf = featurizer.transform(psdf.to_spark(index_col=\"index\"))[\"index\", \"features\"]\n", + "You're a retrieve augmented coding assistant. You answer user's questions based on your own knowledge and the\n", + "context provided by the user.\n", + "If you can't answer the question with or without the current context, you should reply exactly `UPDATE CONTEXT`.\n", + "For code generation, you must obey the following rules:\n", + "Rule 1. You MUST NOT install any packages because all the packages needed are already installed.\n", + "Rule 2. You must follow the formats below to write your code:\n", + "```language\n", + "# your code\n", "```\n", "\n", - "3. Use the Spark ML models in FLAML's AutoML. Include the models you want to try in the `estimator_list` argument to `flaml.AutoML()`. FLAML will start trying configurations for these models. Here is an example code snippet:\n", + "User's question is: How to use spark for parallel training in FLAML? Give me sample code.\n", "\n", - "```python\n", - "import flaml\n", + "Context is: # Integrate - Spark\n", "\n", - "automl = flaml.AutoML()\n", - "settings = {\n", - " \"time_budget\": 30,\n", - " \"metric\": \"r2\",\n", - " \"estimator_list\": [\"lgbm_spark\"],\n", - " \"task\": \"regression\"\n", - "}\n", + "FLAML has integrated Spark for distributed training. There are two main aspects of integration with Spark:\n", "\n", - "automl.fit(\n", - " dataframe=psdf,\n", - " label=label,\n", - " **settings\n", - ")\n", - "```\n", + "- Use Spark ML estimators for AutoML.\n", + "- Use Spark to run training in parallel spark jobs.\n", "\n", - "4. To enable parallel Spark jobs during parallel tuning, set the `use_spark` parameter to `True`. FLAML will dispatch your job to the distributed Spark backend using `joblib-spark`. Here is an example code snippet:\n", + "## Spark ML Estimators\n", "\n", - "```python\n", - "import flaml\n", + "FLAML integrates estimators based on Spark ML models. These models are trained in parallel using Spark, so we called them Spark estimators. To use these models, you first need to organize your data in the required format.\n", "\n", - "automl_experiment = flaml.AutoML()\n", - "automl_settings = {\n", - " \"time_budget\": 30,\n", - " \"metric\": \"r2\",\n", - " \"task\": \"regression\",\n", - " \"n_concurrent_trials\": 2,\n", - " \"use_spark\": True,\n", - " \"force_cancel\": True\n", - "}\n", + "### Data\n", "\n", - "automl.fit(\n", - " dataframe=dataframe,\n", - " label=label,\n", - " **automl_settings\n", - ")\n", - "```\n", + "For Spark estimators, AutoML only consumes Spark data. FLAML provides a convenient function `to_pandas_on_spark` in the `flaml.automl.spark.utils` module to convert your data into a pandas-on-spark (`pyspark.pandas`) dataframe/series, which Spark estimators require.\n", "\n", - "Please note that you should not set `use_spark` to `True` when applying AutoML and Tuning for Spark Data, as SparkML models will be used for Spark Data in AutoML and Tuning.\n", + "This utility function takes data in the form of a `pandas.Dataframe` or `pyspark.sql.Dataframe` and converts it into a pandas-on-spark dataframe. It also takes `pandas.Series` or `pyspark.sql.Dataframe` and converts it into a [pandas-on-spark](https://spark.apache.org/docs/latest/api/python/user_guide/pandas_on_spark/index.html) series. If you pass in a `pyspark.pandas.Dataframe`, it will not make any changes.\n", "\n", - "Let me know if you need anything else.\n", + "This function also accepts optional arguments `index_col` and `default_index_type`.\n", "\n", - "--------------------------------------------------------------------------------\n", - "To use Spark for parallel training in FLAML, you can follow these steps:\n", + "- `index_col` is the column name to use as the index, default is None.\n", + "- `default_index_type` is the default index type, default is \"distributed-sequence\". More info about default index type could be found on Spark official [documentation](https://spark.apache.org/docs/latest/api/python/user_guide/pandas_on_spark/options.html#default-index-type)\n", "\n", - "1. Prepare your data in the required format using the `to_pandas_on_spark` function from the `flaml.automl.spark.utils` module. This function converts your data into a pandas-on-spark dataframe, which is required by Spark estimators. Here is an example code snippet:\n", + "Here is an example code snippet for Spark Data:\n", "\n", "```python\n", "import pandas as pd\n", @@ -607,7 +645,7 @@ "data = {\n", " \"Square_Feet\": [800, 1200, 1800, 1500, 850],\n", " \"Age_Years\": [20, 15, 10, 7, 25],\n", - " \"Price\": [100000, 200000, 300000, 240000, 120000]\n", + " \"Price\": [100000, 200000, 300000, 240000, 120000],\n", "}\n", "\n", "# Creating a pandas DataFrame\n", @@ -618,7 +656,9 @@ "psdf = to_pandas_on_spark(dataframe)\n", "```\n", "\n", - "2. Format your data appropriately for Spark ML models. Use the `VectorAssembler` from `pyspark.ml.feature` to merge all feature columns into a single vector column. Here is an example:\n", + "To use Spark ML models you need to format your data appropriately. Specifically, use [`VectorAssembler`](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.ml.feature.VectorAssembler.html) to merge all feature columns into a single vector column.\n", + "\n", + "Here is an example of how to use it:\n", "\n", "```python\n", "from pyspark.ml.feature import VectorAssembler\n", @@ -629,27 +669,57 @@ "psdf = featurizer.transform(psdf.to_spark(index_col=\"index\"))[\"index\", \"features\"]\n", "```\n", "\n", - "3. Use the Spark ML models in FLAML's AutoML. Include the models you want to try in the `estimator_list` argument to `flaml.AutoML()`. FLAML will start trying configurations for these models. Here is an example code snippet:\n", + "Later in conducting the experiment, use your pandas-on-spark data like non-spark data and pass them using `X_train, y_train` or `dataframe, label`.\n", + "\n", + "### Estimators\n", + "\n", + "#### Model List\n", + "\n", + "- `lgbm_spark`: The class for fine-tuning Spark version LightGBM models, using [SynapseML](https://microsoft.github.io/SynapseML/docs/features/lightgbm/about/) API.\n", + "\n", + "#### Usage\n", + "\n", + "First, prepare your data in the required format as described in the previous section.\n", + "\n", + "By including the models you intend to try in the `estimators_list` argument to `flaml.automl`, FLAML will start trying configurations for these models. If your input is Spark data, FLAML will also use estimators with the `_spark` postfix by default, even if you haven't specified them.\n", + "\n", + "Here is an example code snippet using SparkML models in AutoML:\n", "\n", "```python\n", "import flaml\n", "\n", + "# prepare your data in pandas-on-spark format as we previously mentioned\n", + "\n", "automl = flaml.AutoML()\n", "settings = {\n", " \"time_budget\": 30,\n", " \"metric\": \"r2\",\n", - " \"estimator_list\": [\"lgbm_spark\"],\n", - " \"task\": \"regression\"\n", + " \"estimator_list\": [\"lgbm_spark\"], # this setting is optional\n", + " \"task\": \"regression\",\n", "}\n", "\n", "automl.fit(\n", " dataframe=psdf,\n", " label=label,\n", - " **settings\n", + " **settings,\n", ")\n", "```\n", "\n", - "4. To enable parallel Spark jobs during parallel tuning, set the `use_spark` parameter to `True`. FLAML will dispatch your job to the distributed Spark backend using `joblib-spark`. Here is an example code snippet:\n", + "[Link to notebook](https://github.com/microsoft/FLAML/blob/main/notebook/automl_bankrupt_synapseml.ipynb) | [Open in colab](https://colab.research.google.com/github/microsoft/FLAML/blob/main/notebook/automl_bankrupt_synapseml.ipynb)\n", + "\n", + "## Parallel Spark Jobs\n", + "\n", + "You can activate Spark as the parallel backend during parallel tuning in both [AutoML](/docs/Use-Cases/Task-Oriented-AutoML#parallel-tuning) and [Hyperparameter Tuning](/docs/Use-Cases/Tune-User-Defined-Function#parallel-tuning), by setting the `use_spark` to `true`. FLAML will dispatch your job to the distributed Spark backend using [`joblib-spark`](https://github.com/joblib/joblib-spark).\n", + "\n", + "Please note that you should not set `use_spark` to `true` when applying AutoML and Tuning for Spark Data. This is because only SparkML models will be used for Spark Data in AutoML and Tuning. As SparkML models run in parallel, there is no need to distribute them with `use_spark` again.\n", + "\n", + "All the Spark-related arguments are stated below. These arguments are available in both Hyperparameter Tuning and AutoML:\n", + "\n", + "- `use_spark`: boolean, default=False | Whether to use spark to run the training in parallel spark jobs. This can be used to accelerate training on large models and large datasets, but will incur more overhead in time and thus slow down training in some cases. GPU training is not supported yet when use_spark is True. For Spark clusters, by default, we will launch one trial per executor. However, sometimes we want to launch more trials than the number of executors (e.g., local mode). In this case, we can set the environment variable `FLAML_MAX_CONCURRENT` to override the detected `num_executors`. The final number of concurrent trials will be the minimum of `n_concurrent_trials` and `num_executors`.\n", + "- `n_concurrent_trials`: int, default=1 | The number of concurrent trials. When n_concurrent_trials > 1, FLAML performes parallel tuning.\n", + "- `force_cancel`: boolean, default=False | Whether to forcely cancel Spark jobs if the search time exceeded the time budget. Spark jobs include parallel tuning jobs and Spark-based model training jobs.\n", + "\n", + "An example code snippet for using parallel Spark jobs:\n", "\n", "```python\n", "import flaml\n", @@ -661,99 +731,69 @@ " \"task\": \"regression\",\n", " \"n_concurrent_trials\": 2,\n", " \"use_spark\": True,\n", - " \"force_cancel\": True\n", + " \"force_cancel\": True, # Activating the force_cancel option can immediately halt Spark jobs once they exceed the allocated time_budget.\n", "}\n", "\n", "automl.fit(\n", " dataframe=dataframe,\n", " label=label,\n", - " **automl_settings\n", + " **automl_settings,\n", ")\n", "```\n", "\n", - "Please note that you should not set `use_spark` to `True` when applying AutoML and Tuning for Spark Data, as SparkML models will be used for Spark Data in AutoML and Tuning.\n", + "[Link to notebook](https://github.com/microsoft/FLAML/blob/main/notebook/integrate_spark.ipynb) | [Open in colab](https://colab.research.google.com/github/microsoft/FLAML/blob/main/notebook/integrate_spark.ipynb)\n", "\n", - "Let me know if you need anything else.\n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mSenior_Python_Engineer\u001b[0m (to chat_manager):\n", "\n", - "Here is the sample code to use Spark for parallel training in FLAML:\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mProduct_Manager\u001b[0m (to chat_manager):\n", "\n", "```python\n", - "import pandas as pd\n", + "from flaml.automl import AutoML\n", "from flaml.automl.spark.utils import to_pandas_on_spark\n", "from pyspark.ml.feature import VectorAssembler\n", - "import flaml\n", + "import pandas as pd\n", "\n", - "# Step 1: Prepare your data in the required format\n", + "# Sample data in a dictionary\n", "data = {\n", " \"Square_Feet\": [800, 1200, 1800, 1500, 850],\n", " \"Age_Years\": [20, 15, 10, 7, 25],\n", - " \"Price\": [100000, 200000, 300000, 240000, 120000]\n", + " \"Price\": [100000, 200000, 300000, 240000, 120000],\n", "}\n", "\n", + "# Convert dictionary to pandas DataFrame\n", "dataframe = pd.DataFrame(data)\n", "label = \"Price\"\n", "\n", + "# Convert pandas DataFrame to pandas-on-spark DataFrame\n", "psdf = to_pandas_on_spark(dataframe)\n", "\n", - "# Step 2: Format your data for Spark ML models\n", - "columns = psdf.columns\n", - "feature_cols = [col for col in columns if col != label]\n", + "# Use VectorAssembler to merge feature columns into a single vector column\n", + "feature_cols = [col for col in psdf.columns if col != label]\n", "featurizer = VectorAssembler(inputCols=feature_cols, outputCol=\"features\")\n", - "psdf = featurizer.transform(psdf.to_spark(index_col=\"index\"))[\"index\", \"features\"]\n", + "psdf = featurizer.transform(psdf.to_spark(index_col=\"index\"))[\"index\", \"features\", label]\n", "\n", - "# Step 3: Use Spark ML models in FLAML's AutoML\n", - "automl = flaml.AutoML()\n", - "settings = {\n", - " \"time_budget\": 30,\n", - " \"metric\": \"r2\",\n", - " \"estimator_list\": [\"lgbm_spark\"],\n", - " \"task\": \"regression\"\n", - "}\n", + "# Initialize AutoML instance\n", + "automl = AutoML()\n", "\n", - "automl.fit(\n", - " dataframe=psdf,\n", - " label=label,\n", - " **settings\n", - ")\n", - "\n", - "# Step 4: Enable parallel Spark jobs during parallel tuning\n", - "automl_experiment = flaml.AutoML()\n", + "# AutoML settings\n", "automl_settings = {\n", - " \"time_budget\": 30,\n", - " \"metric\": \"r2\",\n", + " \"time_budget\": 30, # Total running time in seconds\n", + " \"metric\": \"r2\", # Evaluation metric\n", " \"task\": \"regression\",\n", - " \"n_concurrent_trials\": 2,\n", - " \"use_spark\": True,\n", - " \"force_cancel\": True\n", + " \"n_concurrent_trials\": 2, # Number of concurrent Spark jobs\n", + " \"use_spark\": True, # Enable Spark for parallel training\n", + " \"force_cancel\": True, # Force cancel Spark jobs if they exceed the time budget\n", + " \"estimator_list\": [\"lgbm_spark\"] # Optional: Specific estimator to use\n", "}\n", "\n", + "# Run AutoML fit with pandas-on-spark dataframe\n", "automl.fit(\n", - " dataframe=dataframe,\n", + " dataframe=psdf,\n", " label=label,\n", - " **automl_settings\n", + " **automl_settings,\n", ")\n", "```\n", - "\n", - "Let me know if you need anything else.\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mCode_Reviewer\u001b[0m (to chat_manager):\n", - "\n", - "The code you provided is correct and follows the guidelines for using Spark for parallel training in FLAML. It includes the necessary steps to prepare the data, format it for Spark ML models, and use Spark ML models in FLAML's AutoML. It also demonstrates how to enable parallel Spark jobs during parallel tuning.\n", - "\n", - "Great job! You can now terminate the conversation.\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mBoss_Assistant\u001b[0m (to chat_manager):\n", - "\n", - "\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mProduct_Manager\u001b[0m (to chat_manager):\n", - "\n", "TERMINATE\n", "\n", "--------------------------------------------------------------------------------\n" @@ -776,7 +816,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 5, "metadata": {}, "outputs": [ { @@ -788,28 +828,35 @@ "How to use spark for parallel training in FLAML? Give me sample code.\n", "\n", "--------------------------------------------------------------------------------\n", - "How to use spark for parallel training in FLAML? Give me sample code.\n", - "\n", - "--------------------------------------------------------------------------------\n", "\u001b[33mProduct_Manager\u001b[0m (to chat_manager):\n", "\n", - "\u001b[32m***** Suggested function Call: retrieve_content *****\u001b[0m\n", + "\u001b[32m***** Suggested function call: retrieve_content *****\u001b[0m\n", "Arguments: \n", - "{\n", - " \"message\": \"How to use spark for parallel training in FLAML? Give me sample code.\"\n", - "}\n", + "{\"message\":\"using Apache Spark for parallel training in FLAML with sample code\"}\n", "\u001b[32m*****************************************************\u001b[0m\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[35m\n", - ">>>>>>>> EXECUTING FUNCTION retrieve_content...\u001b[0m\n", - "doc_ids: [['doc_0', 'doc_1', 'doc_122']]\n", - "\u001b[32mAdding doc_id doc_0 to context.\u001b[0m\n", - "\u001b[32mAdding doc_id doc_1 to context.\u001b[0m\n", - "\u001b[32mAdding doc_id doc_122 to context.\u001b[0m\n", + ">>>>>>>> EXECUTING FUNCTION retrieve_content...\u001b[0m\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Number of requested results 3 is greater than number of elements in index 1, updating n_results = 1\n", + "Model gpt4-1106-preview not found. Using cl100k_base encoding.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "VectorDB returns doc_ids: [['bdfbc921']]\n", + "\u001b[32mAdding content of doc bdfbc921 to context.\u001b[0m\n", "\u001b[33mBoss\u001b[0m (to chat_manager):\n", "\n", - "\u001b[32m***** Response from calling function \"retrieve_content\" *****\u001b[0m\n", + "\u001b[32m***** Response from calling function (retrieve_content) *****\u001b[0m\n", "You're a retrieve augmented coding assistant. You answer user's questions based on your own knowledge and the\n", "context provided by the user.\n", "If you can't answer the question with or without the current context, you should reply exactly `UPDATE CONTEXT`.\n", @@ -820,11 +867,12 @@ "# your code\n", "```\n", "\n", - "User's question is: How to use spark for parallel training in FLAML? Give me sample code.\n", + "User's question is: using Apache Spark for parallel training in FLAML with sample code\n", "\n", "Context is: # Integrate - Spark\n", "\n", "FLAML has integrated Spark for distributed training. There are two main aspects of integration with Spark:\n", + "\n", "- Use Spark ML estimators for AutoML.\n", "- Use Spark to run training in parallel spark jobs.\n", "\n", @@ -839,6 +887,7 @@ "This utility function takes data in the form of a `pandas.Dataframe` or `pyspark.sql.Dataframe` and converts it into a pandas-on-spark dataframe. It also takes `pandas.Series` or `pyspark.sql.Dataframe` and converts it into a [pandas-on-spark](https://spark.apache.org/docs/latest/api/python/user_guide/pandas_on_spark/index.html) series. If you pass in a `pyspark.pandas.Dataframe`, it will not make any changes.\n", "\n", "This function also accepts optional arguments `index_col` and `default_index_type`.\n", + "\n", "- `index_col` is the column name to use as the index, default is None.\n", "- `default_index_type` is the default index type, default is \"distributed-sequence\". More info about default index type could be found on Spark official [documentation](https://spark.apache.org/docs/latest/api/python/user_guide/pandas_on_spark/options.html#default-index-type)\n", "\n", @@ -847,10 +896,13 @@ "```python\n", "import pandas as pd\n", "from flaml.automl.spark.utils import to_pandas_on_spark\n", + "\n", "# Creating a dictionary\n", - "data = {\"Square_Feet\": [800, 1200, 1800, 1500, 850],\n", - " \"Age_Years\": [20, 15, 10, 7, 25],\n", - " \"Price\": [100000, 200000, 300000, 240000, 120000]}\n", + "data = {\n", + " \"Square_Feet\": [800, 1200, 1800, 1500, 850],\n", + " \"Age_Years\": [20, 15, 10, 7, 25],\n", + " \"Price\": [100000, 200000, 300000, 240000, 120000],\n", + "}\n", "\n", "# Creating a pandas DataFrame\n", "dataframe = pd.DataFrame(data)\n", @@ -863,8 +915,10 @@ "To use Spark ML models you need to format your data appropriately. Specifically, use [`VectorAssembler`](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.ml.feature.VectorAssembler.html) to merge all feature columns into a single vector column.\n", "\n", "Here is an example of how to use it:\n", + "\n", "```python\n", "from pyspark.ml.feature import VectorAssembler\n", + "\n", "columns = psdf.columns\n", "feature_cols = [col for col in columns if col != label]\n", "featurizer = VectorAssembler(inputCols=feature_cols, outputCol=\"features\")\n", @@ -874,10 +928,13 @@ "Later in conducting the experiment, use your pandas-on-spark data like non-spark data and pass them using `X_train, y_train` or `dataframe, label`.\n", "\n", "### Estimators\n", + "\n", "#### Model List\n", + "\n", "- `lgbm_spark`: The class for fine-tuning Spark version LightGBM models, using [SynapseML](https://microsoft.github.io/SynapseML/docs/features/lightgbm/about/) API.\n", "\n", "#### Usage\n", + "\n", "First, prepare your data in the required format as described in the previous section.\n", "\n", "By including the models you intend to try in the `estimators_list` argument to `flaml.automl`, FLAML will start trying configurations for these models. If your input is Spark data, FLAML will also use estimators with the `_spark` postfix by default, even if you haven't specified them.\n", @@ -886,6 +943,7 @@ "\n", "```python\n", "import flaml\n", + "\n", "# prepare your data in pandas-on-spark format as we previously mentioned\n", "\n", "automl = flaml.AutoML()\n", @@ -903,24 +961,25 @@ ")\n", "```\n", "\n", - "\n", "[Link to notebook](https://github.com/microsoft/FLAML/blob/main/notebook/automl_bankrupt_synapseml.ipynb) | [Open in colab](https://colab.research.google.com/github/microsoft/FLAML/blob/main/notebook/automl_bankrupt_synapseml.ipynb)\n", "\n", "## Parallel Spark Jobs\n", + "\n", "You can activate Spark as the parallel backend during parallel tuning in both [AutoML](/docs/Use-Cases/Task-Oriented-AutoML#parallel-tuning) and [Hyperparameter Tuning](/docs/Use-Cases/Tune-User-Defined-Function#parallel-tuning), by setting the `use_spark` to `true`. FLAML will dispatch your job to the distributed Spark backend using [`joblib-spark`](https://github.com/joblib/joblib-spark).\n", "\n", "Please note that you should not set `use_spark` to `true` when applying AutoML and Tuning for Spark Data. This is because only SparkML models will be used for Spark Data in AutoML and Tuning. As SparkML models run in parallel, there is no need to distribute them with `use_spark` again.\n", "\n", "All the Spark-related arguments are stated below. These arguments are available in both Hyperparameter Tuning and AutoML:\n", "\n", - "\n", "- `use_spark`: boolean, default=False | Whether to use spark to run the training in parallel spark jobs. This can be used to accelerate training on large models and large datasets, but will incur more overhead in time and thus slow down training in some cases. GPU training is not supported yet when use_spark is True. For Spark clusters, by default, we will launch one trial per executor. However, sometimes we want to launch more trials than the number of executors (e.g., local mode). In this case, we can set the environment variable `FLAML_MAX_CONCURRENT` to override the detected `num_executors`. The final number of concurrent trials will be the minimum of `n_concurrent_trials` and `num_executors`.\n", "- `n_concurrent_trials`: int, default=1 | The number of concurrent trials. When n_concurrent_trials > 1, FLAML performes parallel tuning.\n", "- `force_cancel`: boolean, default=False | Whether to forcely cancel Spark jobs if the search time exceeded the time budget. Spark jobs include parallel tuning jobs and Spark-based model training jobs.\n", "\n", "An example code snippet for using parallel Spark jobs:\n", + "\n", "```python\n", "import flaml\n", + "\n", "automl_experiment = flaml.AutoML()\n", "automl_settings = {\n", " \"time_budget\": 30,\n", @@ -928,7 +987,7 @@ " \"task\": \"regression\",\n", " \"n_concurrent_trials\": 2,\n", " \"use_spark\": True,\n", - " \"force_cancel\": True, # Activating the force_cancel option can immediately halt Spark jobs once they exceed the allocated time_budget.\n", + " \"force_cancel\": True, # Activating the force_cancel option can immediately halt Spark jobs once they exceed the allocated time_budget.\n", "}\n", "\n", "automl.fit(\n", @@ -938,41 +997,50 @@ ")\n", "```\n", "\n", - "\n", "[Link to notebook](https://github.com/microsoft/FLAML/blob/main/notebook/integrate_spark.ipynb) | [Open in colab](https://colab.research.google.com/github/microsoft/FLAML/blob/main/notebook/integrate_spark.ipynb)\n", "\n", - "2684,4/26/2011,2,0,4,17,0,2,1,1,0.68,0.6364,0.61,0.3582,521\n", - "2685,4/26/2011,2,0,4,18,0,2,1,1,0.68,0.6364,0.65,0.4478,528\n", - "2686,4/26/2011,2,0,4,19,0,2,1,1,0.64,0.6061,0.73,0.4179,328\n", - "2687,4/26/2011,2,0,4,20,0,2,1,1,0.64,0.6061,0.73,0.3582,234\n", - "2688,4/26/2011,2,0,4,21,0,2,1,1,0.62,0.5909,0.78,0.2836,195\n", - "2689,4/26/2011,2,0,4,22,0,2,1,2,0.6,0.5606,0.83,0.194,148\n", - "2690,4/26/2011,2,0,4,23,0,2,1,2,0.6,0.5606,0.83,0.2239,78\n", - "2691,4/27/2011,2,0,4,0,0,3,1,1,0.6,0.5606,0.83,0.2239,27\n", - "2692,4/27/2011,2,0,4,1,0,3,1,1,0.6,0.5606,0.83,0.2537,17\n", - "2693,4/27/2011,2,0,4,2,0,3,1,1,0.58,0.5455,0.88,0.2537,5\n", - "2694,4/27/2011,2,0,4,3,0,3,1,2,0.58,0.5455,0.88,0.2836,7\n", - "2695,4/27/2011,2,0,4,4,0,3,1,1,0.56,0.5303,0.94,0.2239,6\n", - "2696,4/27/2011,2,0,4,5,0,3,1,2,0.56,0.5303,0.94,0.2537,17\n", - "2697,4/27/2011,2,0,4,6,0,3,1,1,0.56,0.5303,0.94,0.2537,84\n", - "2698,4/27/2011,2,0,4,7,0,3,1,2,0.58,0.5455,0.88,0.2836,246\n", - "2699,4/27/2011,2,0,4,8,0,3,1,2,0.58,0.5455,0.88,0.3284,444\n", - "2700,4/27/2011,2,0,4,9,0,3,1,2,0.6,0.5455,0.88,0.4179,181\n", - "2701,4/27/2011,2,0,4,10,0,3,1,2,0.62,0.5758,0.83,0.2836,92\n", - "2702,4/27/2011,2,0,4,11,0,3,1,2,0.64,0.5909,0.78,0.2836,156\n", - "2703,4/27/2011,2,0,4,12,0,3,1,1,0.66,0.6061,0.78,0.3284,173\n", - "2704,4/27/2011,2,0,4,13,0,3,1,1,0.64,0.5909,0.78,0.2985,150\n", - "2705,4/27/2011,2,0,4,14,0,3,1,1,0.68,0.6364,0.74,0.2836,148\n", - "\n", "\n", "\u001b[32m*************************************************************\u001b[0m\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mProduct_Manager\u001b[0m (to chat_manager):\n", + "\u001b[33mBoss\u001b[0m (to chat_manager):\n", + "\n", + "\u001b[32m***** Response from calling function (retrieve_content) *****\u001b[0m\n", + "You're a retrieve augmented coding assistant. You answer user's questions based on your own knowledge and the\n", + "context provided by the user.\n", + "If you can't answer the question with or without the current context, you should reply exactly `UPDATE CONTEXT`.\n", + "For code generation, you must obey the following rules:\n", + "Rule 1. You MUST NOT install any packages because all the packages needed are already installed.\n", + "Rule 2. You must follow the formats below to write your code:\n", + "```language\n", + "# your code\n", + "```\n", + "\n", + "User's question is: using Apache Spark for parallel training in FLAML with sample code\n", + "\n", + "Context is: # Integrate - Spark\n", + "\n", + "FLAML has integrated Spark for distributed training. There are two main aspects of integration with Spark:\n", + "\n", + "- Use Spark ML estimators for AutoML.\n", + "- Use Spark to run training in parallel spark jobs.\n", + "\n", + "## Spark ML Estimators\n", + "\n", + "FLAML integrates estimators based on Spark ML models. These models are trained in parallel using Spark, so we called them Spark estimators. To use these models, you first need to organize your data in the required format.\n", + "\n", + "### Data\n", + "\n", + "For Spark estimators, AutoML only consumes Spark data. FLAML provides a convenient function `to_pandas_on_spark` in the `flaml.automl.spark.utils` module to convert your data into a pandas-on-spark (`pyspark.pandas`) dataframe/series, which Spark estimators require.\n", + "\n", + "This utility function takes data in the form of a `pandas.Dataframe` or `pyspark.sql.Dataframe` and converts it into a pandas-on-spark dataframe. It also takes `pandas.Series` or `pyspark.sql.Dataframe` and converts it into a [pandas-on-spark](https://spark.apache.org/docs/latest/api/python/user_guide/pandas_on_spark/index.html) series. If you pass in a `pyspark.pandas.Dataframe`, it will not make any changes.\n", "\n", - "To use Spark for parallel training in FLAML, you can follow these steps:\n", + "This function also accepts optional arguments `index_col` and `default_index_type`.\n", "\n", - "1. Prepare your data in the required format using Spark data. You can use the `to_pandas_on_spark` function from the `flaml.automl.spark.utils` module to convert your data into a pandas-on-spark dataframe.\n", + "- `index_col` is the column name to use as the index, default is None.\n", + "- `default_index_type` is the default index type, default is \"distributed-sequence\". More info about default index type could be found on Spark official [documentation](https://spark.apache.org/docs/latest/api/python/user_guide/pandas_on_spark/options.html#default-index-type)\n", + "\n", + "Here is an example code snippet for Spark Data:\n", "\n", "```python\n", "import pandas as pd\n", @@ -982,7 +1050,7 @@ "data = {\n", " \"Square_Feet\": [800, 1200, 1800, 1500, 850],\n", " \"Age_Years\": [20, 15, 10, 7, 25],\n", - " \"Price\": [100000, 200000, 300000, 240000, 120000]\n", + " \"Price\": [100000, 200000, 300000, 240000, 120000],\n", "}\n", "\n", "# Creating a pandas DataFrame\n", @@ -993,16 +1061,45 @@ "psdf = to_pandas_on_spark(dataframe)\n", "```\n", "\n", - "2. Use the Spark ML estimators provided by FLAML. You can include the models you want to try in the `estimator_list` argument of the `flaml.AutoML` class. FLAML will start trying configurations for these models.\n", + "To use Spark ML models you need to format your data appropriately. Specifically, use [`VectorAssembler`](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.ml.feature.VectorAssembler.html) to merge all feature columns into a single vector column.\n", + "\n", + "Here is an example of how to use it:\n", + "\n", + "```python\n", + "from pyspark.ml.feature import VectorAssembler\n", + "\n", + "columns = psdf.columns\n", + "feature_cols = [col for col in columns if col != label]\n", + "featurizer = VectorAssembler(inputCols=feature_cols, outputCol=\"features\")\n", + "psdf = featurizer.transform(psdf.to_spark(index_col=\"index\"))[\"index\", \"features\"]\n", + "```\n", + "\n", + "Later in conducting the experiment, use your pandas-on-spark data like non-spark data and pass them using `X_train, y_train` or `dataframe, label`.\n", + "\n", + "### Estimators\n", + "\n", + "#### Model List\n", + "\n", + "- `lgbm_spark`: The class for fine-tuning Spark version LightGBM models, using [SynapseML](https://microsoft.github.io/SynapseML/docs/features/lightgbm/about/) API.\n", + "\n", + "#### Usage\n", + "\n", + "First, prepare your data in the required format as described in the previous section.\n", + "\n", + "By including the models you intend to try in the `estimators_list` argument to `flaml.automl`, FLAML will start trying configurations for these models. If your input is Spark data, FLAML will also use estimators with the `_spark` postfix by default, even if you haven't specified them.\n", + "\n", + "Here is an example code snippet using SparkML models in AutoML:\n", "\n", "```python\n", "import flaml\n", "\n", + "# prepare your data in pandas-on-spark format as we previously mentioned\n", + "\n", "automl = flaml.AutoML()\n", "settings = {\n", " \"time_budget\": 30,\n", " \"metric\": \"r2\",\n", - " \"estimator_list\": [\"lgbm_spark\"], # Optional: specify the Spark estimator\n", + " \"estimator_list\": [\"lgbm_spark\"], # this setting is optional\n", " \"task\": \"regression\",\n", "}\n", "\n", @@ -1013,22 +1110,109 @@ ")\n", "```\n", "\n", - "3. Enable parallel Spark jobs by setting the `use_spark` parameter to `True` in the `fit` method. This will dispatch the job to the distributed Spark backend using `joblib-spark`.\n", + "[Link to notebook](https://github.com/microsoft/FLAML/blob/main/notebook/automl_bankrupt_synapseml.ipynb) | [Open in colab](https://colab.research.google.com/github/microsoft/FLAML/blob/main/notebook/automl_bankrupt_synapseml.ipynb)\n", + "\n", + "## Parallel Spark Jobs\n", + "\n", + "You can activate Spark as the parallel backend during parallel tuning in both [AutoML](/docs/Use-Cases/Task-Oriented-AutoML#parallel-tuning) and [Hyperparameter Tuning](/docs/Use-Cases/Tune-User-Defined-Function#parallel-tuning), by setting the `use_spark` to `true`. FLAML will dispatch your job to the distributed Spark backend using [`joblib-spark`](https://github.com/joblib/joblib-spark).\n", + "\n", + "Please note that you should not set `use_spark` to `true` when applying AutoML and Tuning for Spark Data. This is because only SparkML models will be used for Spark Data in AutoML and Tuning. As SparkML models run in parallel, there is no need to distribute them with `use_spark` again.\n", + "\n", + "All the Spark-related arguments are stated below. These arguments are available in both Hyperparameter Tuning and AutoML:\n", + "\n", + "- `use_spark`: boolean, default=False | Whether to use spark to run the training in parallel spark jobs. This can be used to accelerate training on large models and large datasets, but will incur more overhead in time and thus slow down training in some cases. GPU training is not supported yet when use_spark is True. For Spark clusters, by default, we will launch one trial per executor. However, sometimes we want to launch more trials than the number of executors (e.g., local mode). In this case, we can set the environment variable `FLAML_MAX_CONCURRENT` to override the detected `num_executors`. The final number of concurrent trials will be the minimum of `n_concurrent_trials` and `num_executors`.\n", + "- `n_concurrent_trials`: int, default=1 | The number of concurrent trials. When n_concurrent_trials > 1, FLAML performes parallel tuning.\n", + "- `force_cancel`: boolean, default=False | Whether to forcely cancel Spark jobs if the search time exceeded the time budget. Spark jobs include parallel tuning jobs and Spark-based model training jobs.\n", + "\n", + "An example code snippet for using parallel Spark jobs:\n", "\n", "```python\n", + "import flaml\n", + "\n", + "automl_experiment = flaml.AutoML()\n", + "automl_settings = {\n", + " \"time_budget\": 30,\n", + " \"metric\": \"r2\",\n", + " \"task\": \"regression\",\n", + " \"n_concurrent_trials\": 2,\n", + " \"use_spark\": True,\n", + " \"force_cancel\": True, # Activating the force_cancel option can immediately halt Spark jobs once they exceed the allocated time_budget.\n", + "}\n", + "\n", "automl.fit(\n", - " dataframe=psdf,\n", + " dataframe=dataframe,\n", " label=label,\n", - " use_spark=True,\n", + " **automl_settings,\n", ")\n", "```\n", "\n", - "Note: Make sure you have Spark installed and configured properly before running the code.\n", + "[Link to notebook](https://github.com/microsoft/FLAML/blob/main/notebook/integrate_spark.ipynb) | [Open in colab](https://colab.research.google.com/github/microsoft/FLAML/blob/main/notebook/integrate_spark.ipynb)\n", + "\n", "\n", - "Please let me know if you need any further assistance.\n", + "\u001b[32m*************************************************************\u001b[0m\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mSenior_Python_Engineer\u001b[0m (to chat_manager):\n", + "\u001b[33mProduct_Manager\u001b[0m (to chat_manager):\n", + "\n", + "To use Apache Spark for parallel training in FLAML, you can follow these steps:\n", + "\n", + "1. Ensure your data is in the required pandas-on-spark format.\n", + "2. Use Spark ML estimators by including them in the `estimator_list`.\n", + "3. Set `use_spark` to `True` for parallel tuning.\n", + "\n", + "Here's a sample code demonstrating how to use Spark for parallel training in FLAML:\n", + "\n", + "```python\n", + "import flaml\n", + "from flaml.automl.spark.utils import to_pandas_on_spark\n", + "import pandas as pd\n", + "from pyspark.ml.feature import VectorAssembler\n", + "\n", + "# Sample data in a pandas DataFrame\n", + "data = {\n", + " \"Square_Feet\": [800, 1200, 1800, 1500, 850],\n", + " \"Age_Years\": [20, 15, 10, 7, 25],\n", + " \"Price\": [100000, 200000, 300000, 240000, 120000],\n", + "}\n", + "label = \"Price\"\n", + "\n", + "# Creating a pandas DataFrame\n", + "dataframe = pd.DataFrame(data)\n", + "\n", + "# Convert to pandas-on-spark dataframe\n", + "psdf = to_pandas_on_spark(dataframe)\n", + "\n", + "# Prepare features using VectorAssembler\n", + "columns = psdf.columns\n", + "feature_cols = [col for col in columns if col != label]\n", + "featurizer = VectorAssembler(inputCols=feature_cols, outputCol=\"features\")\n", + "psdf = featurizer.transform(psdf.to_spark(index_col=\"index\"))[\"index\", \"features\"]\n", + "\n", + "# Initialize AutoML\n", + "automl = flaml.AutoML()\n", + "\n", + "# Configure settings for AutoML\n", + "settings = {\n", + " \"time_budget\": 30, # time budget in seconds\n", + " \"metric\": \"r2\",\n", + " \"estimator_list\": [\"lgbm_spark\"], # using Spark ML estimators\n", + " \"task\": \"regression\",\n", + " \"n_concurrent_trials\": 2, # number of parallel trials\n", + " \"use_spark\": True, # enable parallel training using Spark\n", + " \"force_cancel\": True, # force cancel Spark jobs if time_budget is exceeded\n", + "}\n", + "\n", + "# Start the training\n", + "automl.fit(dataframe=psdf, label=label, **settings)\n", + "```\n", + "\n", + "In this code snippet:\n", + "- The `to_pandas_on_spark` function is used to convert the pandas DataFrame to a pandas-on-spark DataFrame.\n", + "- `VectorAssembler` is used to transform feature columns into a single vector column.\n", + "- The `AutoML` object is created, and settings are configured for the AutoML run, including setting `use_spark` to `True` for parallel training.\n", + "- The `fit` method is called to start the automated machine learning process.\n", + "\n", + "By using these settings, FLAML will train the models in parallel using Spark, which can accelerate the training process on large models and datasets.\n", "\n", "TERMINATE\n", "\n", diff --git a/notebook/agentchat_groupchat_customized.ipynb b/notebook/agentchat_groupchat_customized.ipynb index 253a0ec939b..dde124aef7d 100644 --- a/notebook/agentchat_groupchat_customized.ipynb +++ b/notebook/agentchat_groupchat_customized.ipynb @@ -155,8 +155,9 @@ " }, # Please set use_docker=True if docker is available to run the generated code. Using docker is safer than running the generated code directly.\n", ")\n", "\n", + "from typing import Dict, List\n", + "\n", "from autogen import Agent\n", - "from typing import List, Dict\n", "\n", "\n", "def custom_speaker_selection_func(last_speaker: Agent, groupchat: autogen.GroupChat):\n", diff --git a/notebook/agentchat_groupchat_finite_state_machine.ipynb b/notebook/agentchat_groupchat_finite_state_machine.ipynb index e24bb5bbbf3..8ef101f7d91 100644 --- a/notebook/agentchat_groupchat_finite_state_machine.ipynb +++ b/notebook/agentchat_groupchat_finite_state_machine.ipynb @@ -32,18 +32,7 @@ "cell_type": "code", "execution_count": 1, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m23.0.1\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m24.0\u001b[0m\n", - "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n", - "Note: you may need to restart the kernel to use updated packages.\n" - ] - } - ], + "outputs": [], "source": [ "%%capture --no-stderr\n", "%pip install pyautogen[graph]>=0.2.11" @@ -76,7 +65,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "0.2.14\n" + "0.2.25\n" ] } ], @@ -91,12 +80,12 @@ "## Motivation\n", "\n", "\n", - "The current GroupChat class allows transition to any agent (without or without the decision of LLM), some use case might demand for more control over transition. A graph is a possible way to control the transition paths, where each node represents an agent and each directed edge represent possible transition path. Let's illustrate the current transition paths for a GroupChat with five agents." + "The current GroupChat class allows transitioning to any agent (with or without the decision of the LLM), some use cases might demand for more control over transition. A graph is a possible way to control the transition paths, where each node represents an agent and each directed edge represents possible transition paths. Let's illustrate the current transition paths for a GroupChat with five agents." ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ @@ -105,7 +94,7 @@ " \"cache_seed\": 44, # change the seed for different trials\n", " \"config_list\": autogen.config_list_from_json(\n", " \"OAI_CONFIG_LIST\",\n", - " filter_dict={\"model\": [\"gpt-4\", \"gpt-4-0613\", \"gpt-4-32k\", \"gpt-4-32k-0613\", \"gpt-4-1106-preview\"]},\n", + " filter_dict={\"tags\": [\"gpt-4\", \"gpt-4-32k\"]}, # comment out to get all\n", " ),\n", " \"temperature\": 0,\n", "}" @@ -113,12 +102,12 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "metadata": {}, "outputs": [ { "data": { - "image/png": "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", + "image/png": "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KWrb93L/bpjo+fSh5UREVH9coSQiQdjb2yMwMBAODg4wNDSEnp4eHB0d8e677+LcuXO17pgzydMaH4x0Vst+PxzpwjJJRKRmXKEkIo0ReiYbi/emQiZXNOicSolYBKlYhKUvdWWZJCJqAiyURKRRcgpLsXB3CmIzbkMiFj21WNY8P9CxA4LHd4NVe8NmTEpE1HqwUBKRRkq/WYyQU9k4euUWsu+U4tEvMhEeDi0f4twJgV7WcOz0nFAxiYhaBRZKItJ4DypkuH7nASplcuhKxbB93ghGerxvAxFRc2GhJCIiIiKV8CpvIiIiIlIJCyURERERqYSFkoiIiIhUwkJJRERERCphoSQiIiIilbBQEhEREZFKWCiJiIiISCUslERERESkEhZKIiIiIlIJCyURERERqYSFkoiIiIhUwkJJRERERCphoSQiIiIilbBQEhEREZFKWCiJiIiISCUslERERESkEhZKIiIiIlIJCyURERERqYSFkoiIiIhUwkJJRERERCphoSQiIiIilbBQEhEREZFKWCiJiIiISCUslERERESkEhZKIiIiIlIJCyURERERqYSFkoiIiIhUwkJJRERERCphoSQiIiIilbBQEhEREZFKWCiJiIiISCUslERERESkEhZKIiIiIlIJCyURERERqYSFkoiIiIhUwkJJRERERCr5f30zSbqx5HOxAAAAAElFTkSuQmCC", "text/plain": [ "
" ] @@ -146,12 +135,12 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "metadata": {}, "outputs": [ { "data": { - "image/png": "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R6EEgO/QeRZy4cKFXf7u/PzevXtVVlYmSYqOjtbKlSt144036uWXX1Z6enq3cW1F++VoqpMkWROuVmDcRJn8AhQ07lpZwke273N8n9q+3qezoKR0Dct4QMFJ0xU+43bX9tazJ+WUNDxhvJKSvlmXMj4+XjNnztTMmTMVH88i6AA8j0IJABfQ0NCgdevWSZKGDx+uuXPnSpIWLFggi8UiSXr77bfldDp1/Phx1+umTJkif39/188zZszoNnZrzQnXY9vxvTq98peuP211p79+xqnW6vJur7XGpboem4PCXI8dzY2SpBa7o7cfFQDc4md0AAAYqNauXSubzSZJqqmp6VISO5SUlGjXrl1dtplMpm77XS5nq63bNrN1SKf36jQv8PXh9gA/5goA9C8KJQBcwFtvvXVJ+2VlZem+++5z/bx//361tbW5ZjG/XTglyX/4aNfjkNRMRdz08277OFptMvtbe5XZJClhRIj2mr8plQ4HM5YA+haFEgB6UF1drY0bN0qSQkNDtWzZsi7Pt7S06Omnn5Ykvfvuu1q+fLni4uJUVlamiooK3X///brnnnu0fv167d692/W6EUMC1CzJmjBZ5uBwOZrq1Hhgi8xBQxSUMFlOp0P2utNqLj+k1soixTzS84U5FxI/IlghgX4aNmyYa9snn3yiWbNmyWq1Ki0tTeHh4Tpz5oy2b98uqf3iog5fffWVaxH32bNnKzIyslfvD8A3USgBoAerV6+W3W6XJN1www167LHHuu3z5ptvKjc3V6dOndK2bdu0YsUKLVy4UE6nU6tWrdKqVaskSWlpacrPb7+LzfjoMOWbTVKAVRHff1KVa5ZJba1qyPlADTkfdBnfEhbVq8wmkzQnuf01M2bMUGBgoJqbm5WTk6P58+dLkrZu3aqMjAwdPHhQt99+e7cx3n33Xb377rtd9gWAi+FEGwDoQefD3bfcckuP+9x8882ux1lZWVqwYIHeeecdTZgwQQEBARo/frxWrVqlzMxM135zJsa61qEMGjtNox5YrpCJc2QJjZDMfjIHhck/KlGh025V5G3P9iqz0yndm95+FXdERITWrl2ryZMnKygoqFfjAEBvmZwdi6YBANzidDp7vCAnPT1de/bskSTt27dP/3tfi3Yer/bo7RctZpOuSxyhNx+e7rExAeBSMUMJAB6SnZ2tu+++W+vXr1dJSYny8vL005/+1FUmU1JSNGnSJC27LU1+Zs9dCS5JfmaTlt2W5tExAeBSMUMJAB6ybds2zZkzp8fnQkNDtWHDBtci51k5pXp2Tb7H3vu/FqTpzmksWg7AGMxQAoCHJCYm6t5779XYsWMVHByswMBAjRs3Tj/+8Y+Vl5fX5Y45d02L1y9uSPbI+z5zQwplEoChmKEEAANl5ZRq8YcHZXc4e3VOpcVskp/ZpOdvmUiZBGA4CiUAGKyspkmL3s9XdmGVLGbTdxbLjuevHxehZbelKW54cD8mBYCeUSgBYIA4erpBK/eUauuRSpVWN6nzl7NJ7YuWz0mO0r3p8RoXFWpUTADohkIJAANQY7NdxdWNarE7FOBnVsKIEIUEci8KAAMThRIAAABu4SpvAAAAuIVCCQAAALdQKAEAAOAWCiUAAADcQqEEAACAWyiUAAAAcAuFEgAAAG6hUAIAAMAtFEoAAAC4hUIJAAAAt1AoAQAA4BYKJQAAANxCoQQAAIBbKJQAAABwC4USAAAAbqFQAgAAwC0USgAAALiFQgkAAAC3UCgBAADgFgolAAAA3EKhBAAAgFsolAAAAHALhRIAAABuoVACAADALRRKAAAAuIVCCQAAALdQKAEAAOAWCiUAAADcQqEEAACAWyiUAAAAcAuFEgAAAG6hUAIAAMAtFEoAAAC4hUIJAAAAt1AoAQAA4BYKJQAAANxCoQQAAIBb/j93HZ/0yGxRQgAAAABJRU5ErkJggg==", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAApQAAAHzCAYAAACe1o1DAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/H5lhTAAAACXBIWXMAAA9hAAAPYQGoP6dpAABmHUlEQVR4nO3deVhVdeLH8TcXRHYNAcUFFfet1NyzRcUlR80Ux9zTpsmsMUubFFNBDW1atF9NM7abqWhmalqpmRaZOW4omvsCuLDLIshyl98f1k3SSgM5F/i8nseny7nnnPuBJ7if+/2exclms9kQEREREfmTTEYHEBEREZGyTYVSRERERIpFhVJEREREikWFUkRERESKRYVSRERERIpFhVJEREREikWFUkRERESKRYVSRERERIpFhVJEREREikWFUkRERESKRYVSRERERIpFhVJEREREikWFUkRERESKRYVSRERERIpFhVJEREREikWFUkRERESKRYVSRERERIpFhVJEREREikWFUkRERESKRYVSRERERIpFhVJEREREikWFUkRERESKRYVSRERERIpFhVJEREREikWFUkRERESKRYVSRERERIpFhVJEREREikWFUkRERESKRYVSRERERIpFhVJEREREikWFUkRERESKRYVSRERERIpFhVJEREREikWFUkRERESKRYVSRERERIpFhVJEREREikWFUkRERESKxcXoACIiImVJTr6ZM2k5FJituLqYqFfNE8/KejuVik2/ASIiIn/geFI2S3fGs/VoMvHpudiues4JCPL1oFuTAEZ0DKJRdW+jYooYxslms9n+eDUREZGKJyE9l7BPY4k+kYqzyQmL9bffMn9+/u6GfkQ+2Io6vh6lmFTEWCqUIiIi1xG1K55Z6w5http+t0j+mrPJCReTExEDWvBQ+6BbmFDEcahQioiI/MobW4/z8qZjxd7PlF6NebJboxJIJOLYdJa3iIjIVaJ2xZdImQR4edMxVuyKL5F9iTgyFUoRESnzxo8fj5OTk/3f/Pnz/9R+EtJzmbXuUIlmm7nuEAnpuUWWxcTEEB4eTnh4ONu2bfvd7QsLC7njjjuKfH95eXklmlGkuFQoRUSkTCssLGTVqlVFlkVFRf2pfYV9Gov5Jo6XvBFmq42wT2OLLIuJiSEiIoKIiIg/LJT/+te/OHDgQIlmEilpKpQi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n58fa9asoU2bNtdMbcOVC5zfe++9BAYGUqlSJdzd3WnUqBGPPPIIu3fvdpgyCRqhFKmwNm7cSJ8+fbj99tuve/01EZHyyGazXXfkr1OnTuzcuROAvjMXc8Tsd8vv5V2eaIRSpAIqKCggNDSUSpUq2Y8/EhGpCKKjoxk2bBgbN24kLi6O/fv388QTT9jLZJMmTXjzH4NwMZXcWeAALiYnIh9sVaL7dCQ6KUekAho4cCCXLl3izTffJCAgwOg4IiKlxmq1EhUVdc0tGeHKiT0ffPABdf28iBjQgqmrY0vsdWcPaEEdX48S25+j0QilSAWzZs0avvjiC9q3b8/jjz9udBwRkVIVHBzMyJEjadCgAR4eHlSuXJmGDRvy+OOPs3//fvsdcx5qH8SUXo1L5DWf7dWEoe1//+43ZZ2OoRSpQHJzc/H398dsNpOUlETVqlWNjiQi4tCidsUza90hzFbbTR1T6WxywsXkxOwBLcp9mQSNUIpUKP369SM3N5e3335bZVJE5AY81D6Ir56+ly7B1YArRfH3/Px8l+BqfPX0vRWiTIJGKEUqjGXLljFixAi6du1KdHS00XFERMqc40nZLN0Zz9ZjycSn5XJ1gXICgqp50K1xACM7BdEwwNuomIZQoRSpALKysggICMDJyYmUlBS8vLz+eCMREflNOflmzqTlUGC24upiol41T/sdcCqiivudi1Qgffr0IT8/n48//lhlUkSkBHhWdqFFzSpGx3AYOoZSpJx799132bFjByEhIYSGhhodR0REyiFNeYuUY+np6fZbdqWmpuLm5mZ0JBERKYc05S1SjvXs2ZOCggI++eQTlUkREbllNOUtUk69/vrr7N27l379+tGvXz+j44iISDmmKW+RcigxMZE6derg4eFBSkoKrq6uRkcSEZFyTCOUIuVQSEgIZrOZTz/9VGVSRERuORVKkXLmxRdf5NChQwwZMoTu3bsbHUdERCoATXmLlCPx8fEEBwfj4+NDcnIyLi46705ERG49jVCKlCMhISFYLBY2bNigMikiIqVGhVKknJg5cybHjx9nzJgxdO7c2eg4IiJSgWjKW6QcOH78OE2bNqVatWokJiZiMumzooiIlB6964iUAz179sRms/Hll1+qTIqISKnTO49IGTdlyhTi4uIYP348bdu2NTqOiIhUQJryFinDDh06RKtWrQgMDCQh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eTIAH3/8MQsWLKBOnTokJCRw/vx5Ro8ezYgRI9i4cSM//PDDNft3q9cGk0cVrLmZ5Bz8GpO7F+712mCzWTFnJpF/9jCFyaep+ej1T8y5Hq/KLnhWvvIrdNttt9mXf/nll9xzzz24ubnRqlUrqlSpQkpKCt988w1w5eSin/3444/2i7jfe++9+Pv73/Dri4iIOAon28+nyYoYaNGiRYwfPx6AwYMHX3OnHIA2bdoQExMDwFdffUVmZiahoaH8+n/hVq1aERt75U42/v0m4dEyBIDLJ3eRvDryypne1+HsE0DtCe8BV65Dmbn9yohptb6T8Lr9yj7y4g6QtPzKGd13dH+AmC1rAEhNTaV27drk5+cX2efWrVu577772LZtG926dfvdn8HP64qIiJQ1OgBMHMLV090DBgy47jr9+/e3P46KimLQoEGsXLmS5s2b4+rqSrNmzVi2bBk9evSwr2dzrmx/7N6gPYEPL8CzRTecvf3A5ILJ3YdKAcF4tx+I/4NTbypzsL+n/bGfnx9r1qyhTZs2uLu739R+REREyjqNUEqZZbPZrntCTqdOndi5cycAfWcu5ojZr0Rvv+hscqJLcDWWPNKxxPYpIiJSlmmEUsqs6Ohohg0bxsaNG4mLi2P//v088cQT9jLZpEkT3vzHIFxMJXcWOICLyYnIB1uV6D5FRETKMo1QSpn1e8clent7s2nTJjp16kTUrnimro4tsdd9cVArhrbXBctFRER+phFKKbOCg4MZOXIkDRo0wMPDg8qVK9OwYUMef/xx9u/fb79jzkPtg5jSq3GJvOazvZqoTIqIiPyKRiilwojaFc+sdYcwW203dUyls8kJF5MTswe0UJkUERG5DhVKqVAS0nMJ+zSW6BOpOJucfrdY/vz83Q39iHywFXV8PUoxqYiISNmhQikV0vGkbJbujGfrsWTi03K5+pfACQiq5kG3xgGM7BREwwBvo2KKiIiUCSqUUuHl5Js5k5ZDgdmKq4uJetU87XfAERERkT+mQikiIiIixaKzvEVERESkWFQoRURERKRYVChFREREpFhUKEVERESkWFQoRURERKRYVChFREREpFhUKEVERESkWFQoRURERKRYVChFREREpFhUKEVERESkWFQoRURERKRYVChFREREpFhUKEVERESkWFQoRURERKRYVChFREREpFhUKEVERESkWFQoRURERKRYVChFREREpFhUKEVERESkWFQoRURERKRYVChFREREpFhUKEVERESkWFQoRURERKRYVChFREREpFhUKEVERESkWFQoRURERKRYVChFREREpFhUKEVERESkWFQoRURERKRYVChFREREpFhUKEVERESkWFQoRURERKRYVChFREREpFhUKEVERESkWFQoRURERKRYVChFREREpFhUKEVERESkWP4fSWCOlScUWOIAAAAASUVORK5CYII=", "text/plain": [ "
" ] @@ -175,19 +164,19 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "\n" + "\n" ] }, { "data": { - "image/png": "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", + "image/png": "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YjG73U7v3r3ZsGGD68hEq9XK2LFj+eabbzh16pSr7ODBg1m6dGmJ9pctW8a1117r+jskJITk5GTq1atXCU9ZPWiGUkRERDxSVSu+ExISaNCgQZmrqsePH09RUREffPBBiXtGo5G1a9cSFRXFwoULGTNmDNHR0Xz55ZduYRIgPT291PavueYatzPA3333XYXJP9EMpYiIiHjsqquuYsOGDZU2S2m32/Hy8qJfv36sXLnyrOX27dtH69atadu27Tn3xLTb7URFRbF9+3a36waDwbVqu1mzZq7zuv/s9ttv55NPPiEyMpLExEQMBkOFn6k60wyliIiIeKyyZylXrFiBw+EodeufM40aNQqHw8GXX355znIOh6PUbx79/Pxc4fD48eNnrT9kyBAAXnzxRYXJUihQioiIiMfatm1Ljx49WL9+vevMa08Un5Bzrq19fv75Z7Zt28agQYPo0KHDOdv797//zSeffFLieteuXRk7diwAhYWF5Ofnu903F1jZffQkWYZ6+DRqQfdefSv6KDWCXnmLiIhIpUhMTOTyyy+nV69eHu/R2KVLFxISEigsLDxrmdDQUNLT00lPTycoKOic7c2dO5d//etfHD58GJPJhM1mA5yvvDMzM3n77bd55plneOWVVxhx+wN8tjGNFUnppGVZODMoGYCwIH+iI0MY2yOM1o3Kt+F6dadAKSIiIpWmR48ebNq0ib1799K6devzbqdOnTo0aNCAAwcOlHp/+vTpPProozzwwAO888475WrT4XCwceNG5s2bx9y5c8nMzATg5ptvZv78+Ux66kV+yQ3hpF8oJqMBm/3sEan4ft9WDXhpeEeaBflX+BmrEwVKERERqTTFs5S9e/dmzZo159VGTk4OAQEBjBgxwvXq+0xWq5WAgAAMBgPZ2dlue0SWl81mY/ny5YwbN44TJ07w1MdL+TypCKvdcc4g+WcmowEvo4Epw9pzS7ewCo+jutA3lCIiIlJp2rVrR7du3Vi7di379u07rzYWL14MUOKEm2ITJ07EYrHw2muvnVeYBDCZTAwePJhjx45x7d/f5KPdBRRY7RUKkwA2u4MCq50nF+1kxgrPvx29VGmGUkRERCrV7t276dChA3369GH16tUVrj9q1CgWLFhAdnY2AQEBbvcyMjJo3LgxjRs35vDhwx6Pdd7mNJ5ctNPjdoq9OqIjo2vgTKUC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Bnq1CObTO0sPqvLX0KIcERERqRIvDe+Il9FQdsEK8DIaeGl4x0ptUzynQCkiIiJVolmQP1OGta/UNp8b1r7GH3N4MVKgFBERkSpzS7cw/jmoTaW09digSEZ307eTFyN9QykiIiJVbt7mNJ75ZjdWu6NC31SajAa8jAaeG9ZeYfIipkApIiIiF8ShLAuTF+9kdUoGJqPhnMGy+H7fVg14aXhHvea+yClQioiIyAWVfDyXzzamsWJvOmmZFs4MIgacm5ZHtwlhXM8wWoXU/auGKRWgQCkiIiJ/GXOBlQOZZgqtdny8jDQPrq0TcC5BCpQiIiIi4hGt8hYRERERjyhQioiIiIhHFChFRERExCMKlCIiIiLiEQVKEREREfGIAqWIiIiIeESBUkREREQ8okApIiIiIh5RoBQRERERjyhQioiIiIhHFChFRERExCMKlCIiIiLiEQVKEREREfGIAqWIiIiIeESBUkREREQ8okApIiIiIh5RoBQRERERjyhQioiIiIhHFChFRERExCMKlCIiIiLiEQVKEREREfGIAqWIiIiIeESBUkREREQ8okApIiIiIh5RoBQRERERjyhQioiIiIhHFChFRERExCMKlCIiIiLiEQVKEREREfGIAqWIiIiIeESBUkREREQ8okApIiIiIh5RoBQRERERjyhQioiIiIhHFChFRERExCMKlCIiIiLiEQVKEREREfHI/wOfH9zZznvnGAAAAABJRU5ErkJggg==", "text/plain": [ "
" ] @@ -233,12 +222,12 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 9, "metadata": {}, "outputs": [ { "data": { - "image/png": "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", + "image/png": "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dfz4cc2cOVN33XWXoqOjLyhpbdxNOivJtVN/Obu1Uk1ZkUr3xcr5endd36m/amtrVFWUo7NZB1SZm672D176jTmX49/GTS2uc1Hr1q3rblu3bp1GjRolV1dX9enTR61atdJ9992nN998U7W1tfrNb36jp59+WlFRUUpJSZEkDRo0iI8MAgAANs3wz6H86KOP9NBDD0mSbr/9di1duvSix/Tv37/uajkbNmxQUVGRpk6dqp9H79Onj/bu3Svpws+hPJOWqNxl88690/sSmnmY5ffIfyT9/HMon5B739GSpPKMPcr56tw7ut37ROipV9/WnNt6Kz8/X35+fhe9RjIuLk6hoaG/eKUcT09PJSQkcKUcAABg0wx/U875x9233XbbJR9z66231n29aNEiTZkyRV9//bV69eolk8mknj176ssvv1RERETd48J6+9V9DuX1gYPVbtabatE7TM1aekvOLnK+3kPNzV3UcvAktZ38fL0y19ZKdw/1lyR5e3trxYoV6t+/v66//vqLHuvh4aFNmzbpmWeeUefOnWUymWQ2m3XnnXcqMTGRMgkAAGye4Qvl1aitrb3kG3KGDh2qbdu2SZJ27typf+6saPRreTupVsO6tNGXD178jnIAAABHZJOFMiEhQR9++KFmzZqlHj16qLCwUP/617/0wQcfSJK6d++u/fv361hhuUa/adHZX/l4nytWW6uaqgqd+OQRmVs0U79+/dSzZ091795dPXv21E033dSo7zwHAACwBTZZKOPj4xUWFnbJ+1q2bKn169fXfcj5osRMPb9sb6P97mY7FulwzMK675s3b67KynOvzfz66681bdq0y/0oAACAXTL8NZRXo0uXLrr77rsVGBgoNzc3XXfdderatasefvhh7d69+4Ir5swY7K+nx3RrlN/7zJjuWvvei3I+76OCKisr1axZMwUEBGjcuHGN8nsAAABsiU0ulFdjUWKmXl6VrKqa2nq9prKZs5NcnJ30ym29NX3wuTfi/O53v9N//vMfVVdX1z1u/vz5euaZZxo9NwAAgLVzmEIpSUcLyjR7+V5tOpSvZs5Ov1gsf7p/ZFdvzZvcRx293OruO378uLp06VL3UUHNmjVTdXW1pk2bpkWLFl2wYAIAANg7hyqUP0nNOa0vtmUq7sdcZZ4s0/l/ACed+9DysG5m3T3UX13NLS/5HM8//7xe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j6FT1fb2F9Xc3FmSVFtxRimb16r0bJWWLl2q2bNn1z3+3nvv1aZNm7Rp0ybdd999jfOHaUIUSgAAYNPOXyGnTp16wf/Pv3/Hjh06evSoJMnX11dffPGFIiMj9fbbb2vo0KEXPW95+i7VlBVJklw79dN1HXvLycWk67veqGatfM495vBOVf/vMee7PmioWofOklvQELUaNq3u9spTJ3TkZKkGDRqkoKCgutv9/f01YsQIjRgxQv7+tvfxQhRKAABgs06fPq2oqChJkpeXl8LDwyVJU6ZMUbNmzSRJixcvVm1trQ4fPlz3cwMGDFDz5s3rvh82bNhFz11ZcKzu6/LDO5TzxXN1/1UX5fzvnlpVnsy66GddOwbXfe18vUfd1zVnS1VRVXPR422di9EBAAAArtaKFStUXl4uSSooKLigJP4kIyNDW7duveC2n18hpyFqK8svus3Z1f2833XefldbK5OL/e15FEoAAGCzvvrqqyt63KJFi+ouqShJu3btUnV1dd2K+fPCKUnNvTrUfd0iOELetzx50WNqKsvl3Ny1Xpk7tWkhSXJ2/r9ief71xG0RhRIAANikkydPKiYmRpLUsmVLzZs374L7Kyoq9NRTT0mSlixZojfffFMdO3bU0aNHdfz4cc2cOVN33XWXoqOj9f3331/0/K6d+svZrZVqyopUui9Wzte76/pO/VVbW6OqohydzTqgytx0tX/w0m/MuRT361zU4rpz9at169Z1t69bt06jRo2Sq6ur+vTpo1atWikvL08Wi0XSuTcX/WT//v11H+IeEhKitm3bXvHvv1a49CIAALBJH330kR566CFJ0u23337RlXIkqX///kpKSpIkbdiwQUVFRZo6dap+Xn/69OmjvXvPXcmm7S1PyC14tCTpTFqicpfNO/dO70to5mGW3yP/kXTucyiLtpxbTNtEPiH3vueeozxjj3K+OveO7hvCJypp4wpJUn5+vvz8/HT27NkLnjMuLk6hoaGKj49XWFjYL/4Nfnqs0ezvEB8AADiE84+7b7vttks+5tZbb637etGiRZoyZYq+/vpr9erVSyaTST179tSXX36piIiIusfVNvu/yy5eHzhY7Wa9qRa9w9Sspbfk7CLn6z3U3NxFLQdPUtvJV36tcEnq0rZF3dfe3t5asWKF+vfvr+uvv75ez2NtWCgBAIDDqK2tveQbcoYOHapt27ZJkiJfWqCDVd6qrmm8itTM2UnDu7TR5/cPabTntCYslAAAwGFs2rRJv/nNbxQdHa2MjAzt3r1bjz76aF2Z7N69uz54bIpcnBvvXeCS5OLspHmT+zTqc1oTFkoAAOAwful1iS1bttT69es1dOhQLUrM1PPL9jba7319Sh9NH2x7H1h+pVgoAQCAw+jSpYvuvvtuBQYGys3NTdddd526du2qhx9+WLt37667Ys6Mwf56eky3Rvmdz4zpbtdlUmKhBAAAuKxFiZl6eVWyqmpq6/WaymbOTnJxdtIrt/W2+zIpUSgBAAB+0dGCMs1evlebDuWrmbPTLxbLn+4f2dVb8yb3UUcvtyZMahwKJQAAwBVIzTmtL7ZlKu7HXGWeLNP5BcpJkn8bN4V1M+vuof7qam5pVExDUCgBAADqqfRslY6cLFVFVY1MLs7q1KZF3RVwHBGFEgAAAA3Cu7wBAADQIBRKAAAANAiFEgAAAA1CoQQAAECDUCgBAADQIBRKAAAANAiFEgAAAA1CoQQAAECDUCgBAADQIBRKAAAANAiFEgAAAA1CoQQAAECDUCgBAADQIBRKAAAANAiFEgAAAA1CoQQAAECDUCgBAADQIBRKAAAANAiFEgAAAA1CoQQAAECDUCgBAADQIBRKAAAANAiFEgAAAA1CoQQAAECDUCgBAADQIBRKAAAANAiFEgAAAA1CoQQAAECDUCgBAADQIBRKAAAANAiFEgAAAA1CoQQAAECDUCgBAADQIBRKAAAANAiFEgAAAA1CoQQAAECD/D9OkrAlDr4CyQAAAABJRU5ErkJggg==", "text/plain": [ "
" ] @@ -282,12 +271,12 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 10, "metadata": {}, "outputs": [ { "data": { - "image/png": "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Z2cFBQVd9/z69etrw4YNevrpp2/5GsHBwTp48KCqVat2w3NcXFyUk5NzO9ElSb/++mue161cuVLTp0/X1q1bVb58+dteDwAAALhVf36/abVaJUkZGRnq1auXqlevriNHjtzRzaL+XG/kyJGaPXu2rFarOnfurPDwcHl4eORbdgBA/mPLpB1KTExUmzZttGDBAu3du1cxMTFasmSJ3nnnHXXr1k2S9Mgjj+jBBx9U9+7dtX79ep04cUJbt27VuHHjtH37dknSxIkTtWjRIk2cOFGHDh3Svn37NH369Jte+/XXX9fWrVs1fPhw7d69W1FRUVq5cmXuUH3p6t0pf/75Z505c0YJCQm5x2vVqqXly5ffcO3atWurbt26uf+UL19eZrNZdevWVcmSJe/mSwYAAADc1P/+AvbPwliPHj3uuBj2ww8/qHTp0vroo48UEBCg3377TatWraIYBgCFAAUxO+Tl5aVmzZpp5syZatGiherWrauwsDANGTJEH3/8saSrd3Rcs2aNWrRooaefflo1atRQ3759dfLkSZUpU0aS1KpVKy1ZskQRERFq2LCh2rRpo99///2m165fv742b96so0ePKiQkRI0aNdKECRNUrly53HMmT56sEydOqGrVqipdunTu8SNHjujSpUsF8BUBAAAA7s5fv5/907Rp0zRt2rTbXispKUkhISHq0KGDLl++rEmTJunMmTNq1qxZfkQFANwDJpvNZjM6BAAAAAAUpOTk5NxdCSaTSXPnztWQIUNue51JkybprbfeksViUUhIiFasWCFfX9/8jgsAKGAUxAAAAAAUeTabTWbz1Q0yS5YsUa9evW547oEDB1S6dGn5+/vnHtu2bZtCQ0MVFxcnX19fffPNN2rfvn2B5wYAFAyG6gMAAAAoUtKyLDqRmKZsi1UuzmYF+XnqZPRRSdILL7xw02LYqVOn1LRpU9WoUUPbt29XVlaWevfurTVr1shsNuull17S+++/n1tcAwAUTnSIAQAAACj0os6naOG2WG08Eq/YpHT99YcckyRfV6sS9/2sNR+M0X0VbrzFsVu3bvr+++9ltVr1+OOPa+XKlcrKylLDhg21atUqVahQocDfCwCg4FEQAwAAAFBonUpK19jl+7TlWIKczCblWG/8482fz4dUK6WpofVU0Tfv3SBXrFih0NDQPMc8PDz05Zdfqk+fPgWS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Bx8YHQcAUEiwZdJBpaamyt/f3+gYKGK8vLz0xhtvaN++fbpy5Yr+/e9/66GHHtLZs2c1Y8YMlS1bVmXKlNGAAQO0b98+o+MCgF3737lgM2fOZC4YcB3Hjx+XJJUtW9bgJACAwoSCmAOyWq3Kzs5W+fLljY6CIsxsNuupp57Sli1blJmZqZ9++kldu3ZVVlaWvv76a9WvX1/e3t567LHHtG7dOqPjAoDdYC4YcHv+HNlQsWJFg5MAAAoTCmIOKDY2VpJUrVo1g5PAkbRu3VorV65UcnKyDh06pEGDBsnT01Nr165Vx44d5erqqgcffFCff/65LBaL0XEB4J46cuSIevfuLS8vL3Xs2FG//PKLqlWrpg8//FCZmZmKjIxUx44djY4J2KU/R4BUqlTJ4CQAgMKEgpgD2rFjhyTx22UYplatWvr8888VFxenCxcuaMyYMQoMDNS2bds0ZMgQubq6qnbt2po8ebIuX75sdFwAKBDJyckaPXq0AgICVKtWLS1dulReXl4aNWqULl68qMOHD+vFF19kSD7wN86ePStJqly5ssFJAACFCQUxB7R//35JUoMGDQxOAkilSpXS1KlTFRUVpfT0dH344Ydq1KiRjh07pokTJ6pEiRKqWLGiXnjhBZ04ccLouLBjFy5c0HPPPafAwEC5uroqICBA7du31y+//GJ0NAUFBWnWrFm3/bojR46odevWKlOmjNzc3FSlShWNHz9eV65cyf+QuCeYCwbkvz9vNMEMMQDA7aAg5oCioqIkURCD/XFzc9OLL76o7du3KysrS8uWLVPbtm2VlJSkTz75RJUrV5afn5969+6tbdu2GR0XdqZnz57atWuX5s+fr6NHjyoiIkKtWrVSYmJigV0zOzu7wNaWpGLFimnAgAFav369jhw5olmzZulf//qXJk6cWKDXRf774YcfFBISInd3d+aCAfnswoULMpvNMpv50QYAcOtMNpvNZnQI3FshISH69ddfmdOEQuX333/Xe++9px9//FFJSUmSJHd3dz344IN6/vnnFRoayjfCDiw5OVklS5bUpk2b1LJly5ue98orr2jlypXKyspS48aNNXPmzDy/IFi1apUmT56sffv2ycvLSyEhIVq+fLmkq51egwYNUlRUlFasWKEePXpo3rx5ioyM1JgxY7R9+3aVKlVKoaGhmjZtmjw9PdWqVStt3rw5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PHkSuPTaLiIici20y2Q5c8899/DNN9+Qn5+P1Wo1Oo5IqWK325k3bx4ff/wxW7Zs4cKFCwD4+fkRFRXF008/fcWC6iJS9v3RumCPPPIIjz76qNYFEynFfvrpJ3r06MHbb7/N008/bXQcEREpQzRDrJxJTEzExcVFZZjI7zCbzdx1112sWbOG3NxcNmzYwODBg7Hb7Xz77be0adMGLy8voqKiWLRoEXa73ejIInKdMjIyePrppwkMDKRRo0bMnTsXT09Pxo4dS1paGgcPHuTJJ59UGSZSyiUmJgJQs2ZNg5OIiEhZo0KsnDlz5gweHh5GxxApEzp27Mi8efNIT0/n0KFDPPzww/j4+LBq1SoGDBiAu7s7rVu35qOPPqKgoMDouCLyF+x2Ox9++CGhoaH4+vryzjvvkJWVdcW6YL6+vkZHFZGr9OslkyEhIQYnERGRskaXTJYzlStXpmLFikWfponItcvIyODdd99lzpw5xMfHY7fbMZlM1KtXj7/97W889dRTekMtUopER0czceJEYmNjsdlsuLi40K5dO1566SX69u1rdDwRuQGPPvooH330ESkpKVpYX0RErokKsXLGarXSrFmzoh3VROTGXLx4kS+//JLPP/+cHTt2cPHiRQACAwO59dZbee6556hfv77BKUXKH60LJlI+3HHHHcyfP7/owykREZGrpUsmyxG73c7FixepVauW0VFEnIarqysPPvggW7ZsoaCggKVLl9K7d2/Onz/PZ599RoMGDfDx8WHgwIGsW7fO6LgiTi0zM5NnnnnminXBnnzySa0LJuKkUlNTMZvNKsNEROSaqRArRw4fPgxAvXr1DE4i4rz69u3L8uXLOX/+PDt27GDo0KFYrVYWLlxIREQEFSpUoEuXLsyePVuL8osUA7vdzkcffURoaCiVK1fmX//6V9G6YPv27SMpKYl3331XlzGLOKlz587h6upqdAwRESmDVIiVI7/88gsAjRs3NjiJSPnQokULZs2aRXJyMqdOneKpp54iICCA9evXM2zYsKJLmCdPnkxubq7RcUXKlOjoaLp06YKbmxuPPvooCQkJhIeHs3TpUnJycpg3b55e70TKgczMTNzd3Y2OISIiZZAKsXJk7969ADRv3tzgJCLlT/Xq1XnnnXc4evQoOTk5TJ48mcaNG7N//36ef/55vLy8CAkJ4f/+7/84ffq0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AKhVq5bBSURExJlouk8pdDwtBwdgz798nS5LRd9rOk7hhSxS5r8G9kIAfCLuxex2aXdJk8nE7iNn2LRpE8899xx16tQpelxmZiYpKSk39iRERERKkVatWrFr1y42b95MaGgoy5cvJyAggP79+5Oammp0PBH5EydPngTQWoAiIlKsVIiVQgU2OwBmN8/LbredT7/qY9iy00ma/XcuphwDoGKbAXjfcvtlYy7aHYSHhzNp0iQOHz5MXFwcb7zxBsOHD9cukCIi4pTatWvHvn37WL9+PQ0aNGDx4sVUq1aNwYMHa3a0SCmVlJQEqBATEZHipUKsFLJaLv2xmK0VsPgEFN2ef2r/VT3elplM0qznuZiaCIB3+zvw7THqD8/zq0aNGvH3v/+dmTNnYrVarze+iIhIqdepUycOHDjA6tWrqVOnDt9//z1+fn4MGTKErKwso+OJyG/8Woh5enr+xUgREZGrp0KsFAr288T0n689QjsX3X7+54XYzqddMb4wJ4PCC+cBuJh2krOzn8eWcWmxfJ+I+6jcdcQVjzH95zwiIiLlWffu3UlISGDFihUEBQUxZ84cfH19uffee8nNzTU6nogA6enpuLi4GB1DREScjAqxUsjTzUKQ76W1vrzbDsLFuypwaU2xszOfJmvrIi4c20Vu/GbSV33KqX8/RGFWyn/KsL9TmHVp/S/PJl1xq9mYvBP7iv4p/M9uk0F+Hni6aU8FERERgF69enH06FF++OEHqlevzldffUWlSpW4//77ycvLMzqeSLl27tw5Xb0gIiLFzuRwOBxGh5ArvbJ4H19tOU6h3UFBaiIp8yZctuPk/wocOZWCpCOkLXv3T4/r13cslVpEMbxdbV7pd+27VoqIiJQH8+fP58knn+TUqVO4urpy//33M3XqVL0pFzFArVq1yM3NJS3tyislRERErpdmiJVSQ9sFUWi/1FVaqwQReP8HVO4+CreajTG7VwQXCy7eVXEPaYXfrU/hWuXqt6EutDsY1j6opKKLiIiUeYMHD+bkyZN8++23+Pn58cknn1CxYkUef/xxCgoKjI4nUq7k5ORo/TARESl2miFWig3/fAubjqQVFWPFwcVsokMdP756oF2xHVNERMTZffXVVzzzzDMkJyfj5ubGI488wuTJk7FYtPyASEmrUKEC9erVY8+ePUZHERERJ6IZYqXYPwc2xWI2/fXAa2Axm/jnwKbFekwRERFnN3z4cJKSkvjss8/w8vJiypQpVKxYkRdeeAG73W50PBGndvHiRXx8fIyOISIiTkaFWClWy9eDV4t5na8J/ZpQ6z8L9ouIiMi1eeCBB0hNTeXDDz+kQoUKvPnmm3h5eTF+/HgVYyIlpLCwkCpVqhgdQ0REnIwKsVJuSJsgnunZoFiO9WzPhvytjdYOExERuVGPPPII6enpTJkyBVdXVyZOnIi3tzevvfaaijGRYpSRkQFA1apVjQ0iIiJOR4VYGfBYt/q8OagpbhYzLtd4CaWL2YSbxcykQU15tFu9EkooIiJSPo0dO5Zz584xefJkTCYT48aNo1KlSkyePFnFmEgxOHLkCADVq1c3OImIiDgbFWJlxJA2Qax6KoIOdfwA/rIY+/X+DnX8WPVUhGaGiYiIlBCz2cyzzz5LZmYmr732Gg6Hg+effx5fX1/ee+89o+OJlGnHjx8HVIiJiEjxUyFWhtTy9eCrB9oRPbYLw9vVprafB/9bi5mA2n4eDG9Xm1VPdeGrB9ppzTAREZGbwGw289JLL5GVlcX48eMpKChg7Nix+Pr68tFHHxkdT6RMSkxMBKB27doGJxEREWdjcjgcDqNDyPXLybdxLC2HApsdq8VMsJ8nnm7aAl5ERMRodrudl156iXfffZe8vDz8/Px48803GTVqlNHRRMqM559/nsmTJ3Pw4EEaNCiedXVFRERAM8TKPE83C02qV6JlUGWaVK+kMkxERKSUMJvNvPHGG5w/f57/+7//Izs7m9GjR+Pv78/MmTONjidSJpw9exaA4OBgY4OIiIjTUSEmIiIiUoIsFgv/+te/yMrK4vHHHyczM5P77ruPwMBA5syZY3Q8kVItJSUFk8mE1Wo1OoqIiDgZFWIiIiIiN4HVamXq1KmcP3+ehx9+mLS0NIYMGUKNGjVYsGCB0fFESqW0tDRcXFyMjiEiIk5IhZiIiIjITWS1Wvn444/Jyspi5MiRJCcnM2jQIIKCgliyZInR8URKlczMTNzc3IyOISIiTkiFmIiIiIgB3N3d+eKLL8jMzGT48OGcPn2a22+/nZCQEH788Uej44mUCllZWVSoUMHoGCIi4oRUiImIiIgYyMPDg5kzZ5Kens6QIUNITEykd+/e1KtXjzVr1hgdT8RQubm5eHl5GR1DRESckAoxERERkVLA29ubb775hrS0NAYPHszRo0fp3r07DRs2ZMOGDUbHEzFEXl4e3t7eRscQEREnpEJMREREpBTx8fFh3rx5pKSk0K9fPxISEujcuTNNmjRhy5YtRscTuakuXryIr6+v0TFERMQJqRATERERKYV8fX1ZtGgRZ8+epU+fPsTFxdG+fXuaNWvGL7/8YnQ8kRJnt9ux2+34+fkZHUVERJyQCjERERGRUszf359ly5Zx8uRJoqKi2Lt3L7fccgutWrVi9+7dRscTKTGpqakAVKtWzeAkIiLijFSIiYiIiJQB1atXZ+XKlRw7doxu3bqxY8cOmjdvTps2bYiLizM6nkixO3r0KACBgYEGJxEREWekQkxERESkDAkKCuKnn37iyJEjdOrUiW3bttG4cWM6dOhAQkKC0fFEis2xY8cAqFWrlrFBRETEKakQExERESmDQkJCWL9+PfHx8bRv357Y2FgaNGhAly5dimbWiJRlJ0+eBFSIiYhIyVAhJiIiIlKG1a9fn9jYWPbu3Uvr1q1Zv349derUoXv37iQmJhodT+S6nTp1CoC6desanERERJyRCjERERERJ9CkSRO2bt3Krl27aNmyJWvWrCE4OJiePXty+vRpo+OJXLOkpCQAatSoYXASERFxRirERERERJxIs2bN+OWXX9i6dSthYWFER0dTs2ZNbr31VpKTk42OJ3LVUlJSMJlMWCwWo6OIiIgTUiEmIiIi4oRat27N7t272bx5M6GhoSxbtoyAgAD69+9Penq60fFE/lJ6errKMBERKTEqxEREREScWLt27di3bx/r16+nfv36LF68mKpVq3LHHXeQkZFhdDyRP5SZmYmbm5vRMURExEmpEBMREREpBzp16sTBgwdZvXo1ISEhzJ8/Hz8/P+6++26ysrKMjidyhezsbDw8PIyOISIiTkqFmIiIiEg50r17dw4dOsSKFSsICgri22+/xdfXl3vvvZfc3Fyj44kUycnJwcvLy+gYIiLipFSIiYiIiJRDvXr14ujRoyxatIjq1avz1VdfUalSJR544AHy8vKMjidCfn4+Pj4+RscQEREnpUJMREREpBzr168fiYmJzJs3D39/f7744gu8vb0ZM2YMBQUFRseTcuzixYtUrlzZ6BgiIuKkVIiJiIiICIMHD+bUqVN8/fXX+Pn5MW3aNCpWrMgTTzyBzWYzOp6UM3a7HYfDQdWq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wHX/fERExLmoEBMRERGRcqVfv36cOHGCuXPnUrVqVT777DO8vb155JFHLputVN54eXnh5eXFwoULyc/P/8Nxd955J8nJySxfvpzt27fTqlUrevToUbRxwdKlSxk4cCB9+/Zlx44drF69mrZt2152jLfffpvmzZuzY8cOxo0bx+HDh+nduzetW7cGYNSoUWzYsIHHHnsMgO+//56aNWsyYcIEzpw5w5kzZ4qOZTKZmD59ejH/NERExNmZHA6Hw+gQIiIiIiJGmT17Nk8//TRJSUlYrVYefvhh/vWvf2GxWIyOdtPNnz+f0aNHc+HCBVq1akVERARDhgyhWbNmwKVZXLfeeivJycm4ubkVPa5evXo899xzPPjgg3To0IE6deowa9as3z1HcHAwLVu2ZMGCBUW3jRo1ChcXF1q1asXDDz/MwoUL8fPzIyIigpycHNzd3QkODmbs2LGMHTv2suM1atSIN95447JZaH9kxIgRZGRksHDhwmv/4YiIiFPRDDERERERKdeGDh3K2bNnmT59OpUqVWLq1Kl4eXnx7LPPYrPZjI53Uw0ePJjTp0+zePFievfuTUxMDK1atSqagbVr1y6ys7Px8/MrmlHm5eXF0aNHOXz4MAA7d+6kR48ef3qeX2eC/WrXrl1Mnz6dxx9/HIC7776bXr16YbfbOXr06J8e68CBA1dVhomIiPyWCjEREREREeC+++4jOTmZTz75BE9PT95++20qVqzISy+9hN1uNzreTePu7k5UVBTjxo1j06ZNjBgxgpdffhmA7OxsAgMD2blz52X/HDx4kGeffRaAChUq/OU5PD09L/s+Ozubhx56qGgHyNjYWHbt2kVCQgJ169Yt3icoIiKCCjERERERkcs8+OCDpKWl8f777+Pu7s4///lPKlasyKuvvlquirFfNW7cuGhx+1atWnH27FksFgv16tW77J8qVaoA0KxZM1avXn1N52jVqhX79+8vWrusefPmRce1Wq0AWK1WCgsLi/GZiYhIeaZCTERERETkdzz22GOcO3euaD2xV155BW9vb9544w2nLMbS0tLo3r07s2bNYvfu3Rw9epS5c+cyefJk+vfvD0BkZCTh4eEMGDCAlStXcuzYMTZt2sRLL73Etm3bAHj55Zf55ptvePnll4mLi2PPnj1MmjTpT8/9/PPPs2nTJn7++WfMZjMJCQksWrSoaFF9uLT22Lp16zh16hSpqalFtzdq1Oiy9ch+z/79+9m5cyfp6elkZmYWzWwTEZHyS4WYiIiIiMif+L//+z/OnTvHG2+8AcCLL76Ij48P//rXv3Cm/am8vLxo164dU6ZMoUuXLoSFhTFu3DhGjx7NBx98AFza0XHZsmV06dKFkSNH0qBBA4YMGcLx48epVq0aAF27dmXu3LksXryYFi1a0L17d37++ec/PXezZs1Yu3Yt58+fx26307JlS8aPH0/16tWLxkyYMIFjx45Rt25dqlatWnT7wYMHyczM/NPj9+3bl5YtW/LDDz8QExNDy5Ytadmy5fX+qERExAlol0kRERERkatkt9t57bXXmDRpErm5uVSqVImJEycWLQYvNyYoKIicnBzS0tKMjiIiIk5OM8RERERERK6S2Wxm/PjxnD9/npdeeon8/HyeeOIJ/Pz8+Pe//210vDIvOzv7igX3RURESoIKMRERERGRa2Q2m3nttdc4f/48zz77LDk5OTz00ENUrVqVL7/80uh4ZVZeXh4VK1Y0OoaIiJQDKsRERERERK6TxWJh8uTJZGdnM3bsWLKysrj//vupVq0aX3/9tdHxypyCggIqV65sdAwRESkHVIiJiIiIiNwgi8XClClTOH/+PI888gjnzp1j6NChVK9enblz5xodr8woLCzEz8/P6BgiIlIOqBATERERESkmVquVDz/8kKysLEaPHk1KSgp33XUXtWrVYtGiRUbHK9XOnz8PgL+/v8FJRESkPFAhJiIiIiJSzNzd3fn3v/9NZmYm9913H2fOnGHAgAHUrl2b5cuXGx2vVDpy5AgAgYGBBicREZHyQIWYiIiIiEgJ8fDwYPr06WRkZHDPPfdw6tQp+vbtS926dVm9erXR8UqVY8eOAVCjRg1jg4iISLmgQkxEREREpIR5eXkxe/Zs0tPTufPOOzl27BiRkZE0aNCAtWvXGh2vVEhMTAQgKCjI4CQiIlIeqBATEREREblJvL29+e6770hJSWHgwIEcPnyYrl27EhoayqZNm4yOZ6jTp08DEBISYnASEREpD1SIiYiIiIjcZL6+vnz//fckJSVx2223cfDgQTp27EjTpk3ZunWr0fEMcebMGQCCg4ONDSIiIuWCCjEREREREYNUqVKFH374gdOnT9O7d2/27dtH27ZtadGiBTt37jQ63k2VkpICXNqQQEREpKSpEBMRERERMVhAQADLly8nMTGRHj16sHv3blq2bEnr1q3Zt2+f0fFuirS0NCwWi9ExRESknFAhJiIiIiJSStSsWZNVq1Zx9OhRIiIi2L59O2FhYbRv356DBw8aHa9EZWZmYrVajY4hIiLlhAoxEREREZFSpnbt2sTExHDo0CE6dOjAli1baNSoER07duTw4cNGxysRWVlZVKhQwegYIiJSTqgQExEREREpperWrcvGjRs5cOAAbdu2ZdOmTdSrV4+uXbty/Phxo+MVq9zcXLy8vIyOISIi5YQKMRERERGRUq5hw4Zs2bKFPXv2cMstt7B27VpCQkKIjIzk1KlTRscrFnl5eXh7exsdQ0REygkVYiIiIiIiZURYWBjbtm1jx44dNGvWjNWrV1OrVi369OnD2bNnjY53QwoKCqhcubLRMUREpJxQISYiIiIiUsa0aNGCnTt38vPPP9OkSRNWrFhB9erVuf3220lNTTU63nWx2+1UqVLF6BgiIlJOqBATERERESmj2rRpw549e9iwYQMNGzZkyZIlVKtWjUGDBnHu3Dmj4121tLQ0APz9/Q1OIiIi5YUKMRERERGRMq5jx47ExcURExND3bp1WbBgAVWqVOGuu+4iKyvL6Hh/6ciRIwAEBgYanERERMoLFWIiIiIiIk4iIiKC+Ph4Vq5cSXBwMHPnzsXX15dhw4aRnZ1tdLw/dOzYMQBq1qxpbBARESk3VIiJiIiIiDiZqKgoDh8+zNKlS6lRowazZ8/Gx8eHkSNHkpuba3S8Ih999BFvvvkm3333HQDnzp1j9+7dpSqjiIg4J5PD4XAYHUJERERERErOwoULefzxxzl58iQWi4WRI0cydepU3N3dDctkt9vx8vLiwoULV9zXs2dPfvzxRwNSiYhIeaEZYiIiIiIiTm7AgAGcOHGC7777jqpVq/Lpp5/i7e3No48+SkFBgSGZzGYzw4cPx2KxXHHfwIEDDUgkIiLliWaIiYiIiIiUM7NmzeLpp58mOTkZq9XKI488wltvvfW75VRJ2rVrFy1atCj63sXFhSZNmvDLL7/g4uJyU7OIiEj5ohliIiIiIiLlzLBhw0hKSuKLL77A29ubd999Fy8vL55//nkKCwtvWo7mzZvTrl27ou8LCwuZNm2ayjARESlxKsRERERERMqpkSNHkpKSwrRp0/D09GTy5MlUrFiRcePGYbfbb0qGJ554oujr++67j/Dw8JtyXhERKd90yaSIiIiIiAAwdepUxo8fT2ZmJh4eHjz//PP84x//wGwuuc/R8/PzqVChAg6Hg7Nnz1KtWrUSO5eIiMivNENMRERERESAS7O10tPTefvtt3FxceHll1+mUqVKvPnmmyUyYywn38ahtDyCWnahecStePn4Ffs5REREfo9miImIiIiIyBXsdjuTJk3i9ddfJycnB29vb1555RWeeuqpy8ZlZGRw6NAhWrdufVXHTUg6z+wtiaw5mExiei6/fTNiAoJ8PejW0J+h7YKoX61i8T0hERGR31AhJiIiIiIif8hutzNx4kQmT55Mbm4uPj4+vPbaazz66KMADBgwgB9++IH169fToUOHPzzOifRcXlywh/WHUnExmyi0//HbkF/v71yvCv8c2JRavh7F/rxERKR8UyEmIiIiIiJ/yW63M27cON555x3y8vLw9fVlzJgxvP7665hMJgIDA9mzZw++vr5XPPbbrYm8vHgfNrvjT4uw/+ViNmExm3i1XxOGtAkqzqcjIiLlnAoxERERERG5ajabjRdeeIH333+f/Pz8ottdXFy49dZbWbhwISaTqej2D9Yk8PbK+Bs+7zM9G/BYt/o3fBwRERFQISYiIiIiItchNjb2dy+RfP/993nssceASzPD/v79nmI756RBTfmbZoqJiEgxUCEmIiIiIiLXrFevXqxcufJ37/v444+59a57iZyylnybnbQVH5C9c0XR/T4R91Ep/M4rHmfPzyVz0xxyD27Edj4Vs5sXFUJaUKnTUFwrB+JmMbPqqQitKSYiIjfMbHQAEREREREpe1JTU3Fzc8PV1RVXV1dcXFyKLpUcM2YMA9/4DpvdgaPQRu7BTZc9Nidu3RXHs+fncnbWc2RtmY8t4ywU2rDnZpCzL4azM56iIPkYNruDFxcU34wzEREpv1SIiYiIiIjINdu+fTt5eXkUFBRQUFCAzWbDbrdTUFDAPz/8klRLVQrtDvKO7cB+Ieuyx15MPsrFtBOX3ZaxYTYXU44B4FYrjKqD/oFXi94A2POySVv+HoV2B+sPpXIo+fxNeY4iIuK8VIiJiIiIiEixcXV1Jb9mG1zMl2aL5ez/72wwj9AuRV//9nZH4UVydq/6z3cmqvR/Do8G7fHt9SgWv5oAFJxJIP/sIVzMJmZtTiz5JyIiIk5NhZiIiIiIiBSrNQeTKbQ7cNgKyE3YDIDZoxK+kaPB7AJATtz6ovEFKcex5+cAYKnkj8XLFwCTyYRb9UZF4/JP7KPQ7mBNfPLNeioiIuKkVIiJiIiIiEixyc63kZieC0DuoZ9xFFwAwKN+e1w8K+Me1BQAW/pJCs4eBqAw878Fl9nT57Ljufzme1vGWQAS03LJybeV1FMQEZFyQIWYiIiIiIgUm+NpOfy6jX3ubxbP92jU8dK/G3Ysuu3XxfXtF/OKbjO5uF52PJPZUvS142L+pX8Dx9JyijO2iIiUMyrERERERESk2BTY7MClXSMvHN4GgNm9Iu61mwPg0bADmC69DcmJW4/D4cDs6l70eEfhxcuO57D/dyaYydXtivOIiIhcD8tfDxEREREREbk6Vsulsis3YTMOWwEA9rzzJE7uf8XYwqxk8k8dwKWS/39vy8m4fEz2uaKvLT4BV5xHRETkeuhVREREREREik2wnycmIGf/2qsanxu3DmvV2pjcPIFL64nZzqcC4HA4yD99sGisW60mAJj+cx4REZHrpRliIiIiIiJSbDzdLAS6XeTYsZ0AmKwV8Im49/JBhTbO/fQ5ALkHNlA5cjRezSI5v3UR4CB10Vt4txvEhcNbsaWfBMAaUB+3gHoABPl54OmmtzIiInL99CoiIiIiIiLFyjd5B9gLAagQ0hLvW26/Ykz23jVcTD5CYc458o7vxqfTUPKO7eJiyjHyT+4j5eS+orFmN0/8+j4JgIvZRLcG/lccT0RE5FrokkkRERERESlWKTt/Kvq6Qr12vzvGo17boq9z49ZhdvMgYNhkvNsNwlKpGrhYMHv44NE4goARU7D6BwNQaHcwrH1QieYXERHnZ3I4HI6/HiYiIiIiInL1hn++hU1H0ii0F9/bDReziQ51/Pjqgd8v2URERK6WZoiJiIiIiEix++fApljMpuI7oMOByWHnnwObFt8xRUSk3FIhJiIiIiIixa6Wrwev9mtSfAc0mUhaOpU61SrRr18/3njjDVasWEFSUlLxnUNERMoNXTIpIiIiIiIl5oM1Cby9Mv6Gj/Nsz4a8P+Y2Dh48CIDZbMZutwPg7+/PZ599xu23X7l4v4iIyO/RDDERERERESkxj3Wrz5uDmuJmMeNyjZdQuphNuFnMTBrUlEe71WPJkiWYTJeO8WsZBpCcnEyFChWKNbeIiDg3FWIiIiIiIlKihrQJYtVTEXSo4wfwl8XYr/d3qOPHqqci+FubS7tK1qtXj3vuuQcXF5fLxj/zzDNERkaWQHIREXFWumRSRERERERumoSk88zeksia+GQS03L57ZsRh8OBr7WQ/q3rMax9EPX8K17x+AMHDtC4cWMcDgcmkwmHw4GXlxdr1qyhdevWN++JiIhImaZCTEREREREDJGTb+NYWg4FNjtWi5nWDWvj6WYhJSWl6NLI33PnnXcyb948qlatyrPPPsvf//53HA4HkyZN4tlnn72Jz0BERMoqFWIiIiIiImK4ixcv4ubmhsPh4PXXX+fFF1/8w7H79u2jX79+TJ8+nc6dOxMXF0enTp1IT08nMjKS5cuXY7FYbmJ6EREpa1SIiYiIiIiI4WJjY+nQoQMAJpOJRYsWXdOukQUFBfTs2ZO1a9dStWpVNm7cSP369UsqroiIlHFaVF9ERERERAwXExOD2Xzp7YnD4WDIkCHExcVd9eOtVisxMTFMnDiR1NRUQkND+fLLL0sqroiIlHEqxERERERExHA//fQTv714JT8/n1tvvZWMjIxrOs4//vEPYmNjqVChAvfffz933XUXdru9mNOKiEhZp0JMREREREQMdfHiRTZs2MD/ruZy9OhRZs6cec3Ha9euHWfOnKFFixbMnTuXkJAQzp49W1xxRUTECagQExERERERQx04cIC8vDyAot0ln3jiCebNm8dDDz10Xcf08vJix44djB07lsTERGrXrs3ixYuLLbOIiJRtKsRERERERMRQDRs25Ouvv2bz5s289tprAHTv3p3Bgwfj5uZ2Q8eeMmUKy5Ytw2Qy0b9/fx555JHiiCwiImWcdpkUEREREZFS4+TJk9SqVYsRI0YU66L4ycnJhIeHc+TIERo3bszGjRvx8fEptuOLiEjZohliIiIiIiJSatSsWRN3d3diY2OL9bj+/v4kJCQwfPhw9u/fT40aNdiwYUOxnkNERMoOFWIiIiIiIlKqBAcHc+zYsWI/rtlsZubMmcyaNYuCggK6dOnC+PHji/08IiJS+qkQExERERGRUqVjx47k5+eTmJhYIscfOnQo8fHxVKtWjYkTJ9KhQ4eiRf1FRKR8UCEmIiIiIiKlyuDBgwGYPXt2iZ0jJCSEU6dO0adPH2JjYwkMDGT37t0ldj4RESldVIiJiIiIiEip0rNnT0wmEytXrizR85jNZpYtW8bUqVPJysqiZcuWvP/++yV6ThERKR20y6SIiIiIiJQ6/v7+OBwOUlJSbsr5du/eTUREBBkZGfTp04clS5ZgNmv+gIiIs9JveBERERERKXWaNm1KWloaNpvtppyvWbNmnDlzhvDwcJYvX06NGjVKZGF/EREpHVSIiYiIiIhIqdOrVy8cDgcrVqy4aed0d3dn06ZNjBs3jqSkJOrXr1+i65iJiIhxVIiJiIiIiEipc8899wAwf/78m37uCRMmEBMTg9VqZdiwYQwbNgy73X7Tc4iISMnRGmIiIiIiIlIqVahQgdq1a3PgwAFDzp+VlUWHDh3Yt28fderUITY2Fn9/f0OyiIhI8dIMMRERERERKZWCg4MNXcfL29ubvXv3MmbMGI4cOUJQUBDLly83LI+IiBQfFWIiIiIiIlIqdejQgfz8fBITEw3N8dFHH7Fo0SIcDgd9+/blqaeeMjSPiIjcOBViIiIiIiJSKg0ePBiAr7/+2uAk0K9fP44fP05QUBDvvvsuLVq0IDs72+hYIiJynVSIiYiIiIhIqdSzZ09MJhMrV640OgoAAQEBHD16lLvuuotdu3YREBBAbGys0bFEROQ6qBATEREREZFSyWKxUKVKFfbs2WN0lCJms5k5c+bwxRdfkJeXR8eOHXn99deNjiUiItdIu0yKiIiIiEip1aNHD9asWUNBQQEWi8XoOJdJSEigY8eOpKSk0KVLF6Kjo7FarUbHEhGRq6AZYiIiIiIiUmr16tULh8PBjz/+aHSUK9SvX5/Tp08TGRnJunXrCAwMJC4uzuhYIiJyFVSIiYiIiIhIqXXPPfcAMH/+fIOT/D6LxUJ0dDSTJ0/m3LlzhIWF8cknnxgdS0RE/oIumRQRERERkVLN3d2dkJCQUj/7atu2bfTo0YOsrCz69+/P999/j9msOQgiIqWRCjERERERESnVQkNDOXr0KHl5eUZH+Uu5ublERESwbds2atSoQWxsLLVq1TI6loiI/A99XCEiIiIiIqVaeHg4+fn5JCYmGh3lL3l4eLB161aee+45Tp06Rd26dZk7d67RsURE5H+oEBMRERERkVLtjjvuAODrr782OMnVmzRpEqtWrcLFxYW77rqLUaNGGR1JRER+Q5dMioiIiIhIqWaz2bBarXTt2pWffvrJ6DjXJD09nfDwcOLj42nQoAGxsbH4+voaHUtEpNzTDDERERERESnVLBYLfn5+7Nmzx+go18zX15eDBw/ywAMPEB8fT40aNVi9erXRsUREyj0VYiIiIiIiUuo1bdqUtLQ0bDab0VGuy2effcbcuXMpLCwkMjKS559/3uhIIiLlmgoxEREREREp9Xr27InD4WDlypVGR7lud9xxB4cOHaJGjRpMnjyZNm3akJuba3QsEZFySYWYiIiIiIiUesOGDQNg3rx5Bie5MUFBQSQmJjJgwAC2bdtGYGAg27ZtMzqWiEi5o0JMRERERERKvZo1a+Lm5kZsbKzRUW6Y2WxmwYIFTJs2jezsbNq2bctbb71ldCwRkXJFu0yKiIiIiEiZEBoaytGjR8nLyzM6SrGJi4ujU6dOpKen06NHD1asWIHFYjE6loiI09MMMRERERERKRPCw8PJz8/nxIkTRkcpNqGhoZw5c4aIiAhWr15N9erVSUhIMDqWiIjTUyEmIiIiIiJlwuDBgwH4+uuvDU5SvKxWKzExMbz22mukpqYSGhrKl19+aXQsERGnpksmRURERESkTLDZbFitVrp168bq1auNjlMitmzZQmRkJNnZ2dx55518++23mM2axyAiUtxUiImIiIiISJlRtWpVTCYTycnJRkcpMdnZ2XTu3JmdO3cSFBTE5s2bCQwMNDqWiIhT0UcNIiIiIiJSZjRt2pTU1FRsNpvRUUqMl5cXO3bsYOzYsSQmJhIcHMzixYuNjiUi4lRUiImIiIiISJnRs2dPHA4HK1euNDpKiZsyZQrLli3DZDLRv39/Hn30UaMjiYg4DRViIiIiIiJSZgwdOhSAefPmGZzk5ujTpw+JiYnUqVOHjz76iCZNmpCZmWl0LBGRMk+FmIiIiIiIlBm1atXCzc2N2NhYo6PcNP7+/iQkJDB8+HD2799P9erVWb9+vdGxRETKNBViIiIiIiJSpgQHB3Ps2DGjY9xUZrOZmTNnMmvWLAoKCoiIiGD8+PFGxxIRKbNUiImIiIiISJkSHh5OXl4eJ0+eNDrKTTd06FDi4+OpVq0aEydOpEOHDuTl5RkdS0SkzFEhJiIiIiIiZcrgwYMB+Prrrw1OYoyQkBBOnTpF3759iY2NJTAwkN27dxsdS0SkTFEhJiIiIiIiZUrv3r0xmUz8+OOPRkcxjNlsZunSpUydOpWsrCxatmzJ1KlTjY4lIlJmmBwOh8PoECIiIiIiIteiatWqmEwmkpOTjY5iuN27dxMREUFGRgZ9+vThhx9+wMXFxehYIiKlmmaIiYiIiIhImdO0aVNSU1MpLCw0OorhmjVrxpkzZwgPD2f58uXUrFmz3G06ICJyrVSIiYiIiIhImRMVFYXD4WDlypVGRykV3N3d2bRpE+PGjSMpKYn69esze/Zso2OJiJRaKsRERERERKTMGTp0KADz5883OEnpMmHCBGJiYrBarQwbNoxhw4Zht9uNjiUiUupoDTERERERESmT3N3dCQkJIS4uzugopU5WVhYdOnRg37591KlTh9jYWPz9/Y2OJSJSamiGmIiIiIiIlEnBwcFaK+sPeHt7s3fvXsaMGcORI0cICgpi+fLlRscSESk1VIiJiIiIiEiZFB4eTl5eHidPnjQ6Sqn10UcfsWjRIhwOB3379uWpp54yOpKISKmgQkxERERERMqkwYMHA/D1118bnKR069evH8ePHycoKIh3332XFi1akJ2dbXQsERFDqRATEREREZEyqXfv3phMJn788Uejo5R6AQEBHD16lLvuuotdu3YREBBAbGys0bFERAyjQkxERERERMoki8WCn58fe/bsMTpKmWA2m5kzZw5ffPEFeXl5dOzYkddff93oWCIihtAukyIiIiIiUmZ169aNtWvXYrPZMJv1ef/VSkhIoGPHjqSkpNClSxeio6OxWq1GxxIRuWn0iiEiIiIiImVWz549cTgcumzyGtWvX5/Tp08TGRnJunXrCAwMJC4uzuhYIiI3jQoxEREREREps4YOHQrA/PnzDU5S9lgsFqKjo5k8eTLnzp0jLCyMadOmGR1LROSm0CWTIiIiIiJSprm7uxMSEqIZTjdg27Zt9OjRg6ysLPr378/333+vS1BFxKmpEBMRERERkTKtUaNGHD9+nAsXLhgdpUzLzc0lIiKCbdu2Ub16dTZv3kytWrWMjiUiUiJU+YuIiIiISJkWHh5OXl4ep06dMjpKmebh4cHWrVt57rnnOH36NHXr1mXu3LlGxxIRKREqxEREREREpEwbOHAgAF9//bXBSZzDpEmTWLVqFS4uLtx111088MADRkcSESl2umRSRERERETKNJvNhtVqpXv37qxatcroOE4jPT2d8PBw4uPjadCgAbGxsfj6+hodS0SkWGiGmIiIiIiIlGkWiwVfX192795tdBSn4uvry8GDB3nggQeIj4+nRo0arF692uhYIiLFQoWYiIiIiIiUeU2bNiU1NRW73W50FKfz2WefMXfuXAoLC4mMjOT55583OpKIyA1TISYiIiIiImVez549cTgc/Pjjj0ZHcUp33HEHhw4dokaNGkyePJnWrVuTm5trdCwRkeumQkxERERERMq8e+65B4D58+cbnMR5BQUFkZiYyIABA9i+fTuBgYFs27bN6FgiItdFhZiIiIiIiJR5tWvXxs3NjU2bNhkdxamZzWYWLFjAtGnTyM7Opm3btrz11ltGxxIRuWbaZVJERERERJxCw4YNSUxM5MKFC0ZHKRfi4uLo1KkT6enp9OjRgxUrVmCxWIyOJSJyVTRDTEREREREnEJ4eDh5eXmcPn3a6CjlQmhoKGfOnCEiIoLVq1dTvXp1EhISjI4lInJVVIiJiIiIiIhTGDRoEACzZ882OEn5YbVaiYmJ4bXXXiM1NZXQ0FC++OILo2OJiPwlXTIpIiIiIiJOwWaz4erqSo8ePVi1apXRccqdLVu2EBkZSXZ2NnfeeSfffvstZrPmYIhI6aRCTEREREREnEaVKlUwm80kJycbHaVcys7OpnPnzuzcuZOgoCA2b95MYGCg0bFERK6gul5ERERERJxGWFgYqamp2O12o6OUS15eXuzYsYOxY8eSmJhIcHAwixcvNjqWiMgVVIiJiIiIiIjTiIqKwuFwEB0dbXSUcm3KlCksW7YMk8lE//79eeSRR4yOJCJyGRViIiIiIiLiNIYNGwbAvHnzDE4iffr04cSJE9SpU4ePP/6YJk2akJmZaXQsERFAhZiIiIiIiDiR2rVr4+bmxsaNG42OIkDVqlVJSEhg+PDh7N+/n+rVq7N+/XqjY4mIqBATERERERHnUrt2bY4ePWp0DPkPs9nMzJkzmTVrFgUFBURERDB+/HijY4lIOadCTEREREREnEp4eDh5eXmcPn3a6CjyG0OHDiU+Pp5q1aoxceJEOnToQF5entGxRKScUiEmIiIiIiJOZdCgQQB8/fXXBieR/xUSEsKpU6fo27cvsbGxBAQEsHv3bqNjiUg5pEJMREREREScSt++fQH48ccfDU4iv8dsNrN06VKmTp3K+fPnadmyJVOnTjU6loiUMyaHw+EwOoSIiIiIiEhxqlKlCi4uLiQlJRkdRf7E7t27iYiIICMjgz59+vDDDz/g4uJidCwRKQc0Q0xERERERJxOWFgYKSkp2O12o6PIn2jWrBlnzpwhPDyc5cuXU7NmTY4dO2Z0LBEpB1SIiYiIiIiI04mKisLhcBAdHW10FPkL7u7ubNq0iXHjxpGUlET9+vWZPXu20bFExMmpEBMREREREaczbNgwAObNm2dwErlaEyZMYO3atVitVoYNG8awYcM0w09ESozWEBMREREREafk5uZGvXr12Ldvn9FR5BpkZmbSsWNH9u3bR506dYiNjcXf39/oWCLiZDRDTEREREREnFJwcDBHjhwxOoZco0qVKrF3717GjBnDkSNHCAoKYvny5UbHEhEno0JMREREREScUvv27cnLy+P06dNGR5Hr8NFHH7Fo0SIcDgd9+/Zl7NixRkcSESeiQkxERERERJzSoEGDAPjmm28MTiLXq1+/fhw/fpygoCDee+89WrRoQXZ2ttGxRMQJqBATERERERGndOuttwKwYsUKg5PIjQgICODo0aPcdddd7Nq1i4CAAGJjY42OJSJlnAoxERERERFxShaLBT8/P3bv3m10FLlBZrOZOXPm8MUXX5CXl0fHjh15/fXXjY4lImWYdpkUERERERGn1bVrV9atW4fNZsNs1nwAZ5CQkEDHjh1JSUmhS5cuREdHY7VajY4lImWMXhFERERERMRpRUVF4XA4WLVqldFRpJjUr1+f06dPExkZybp16wgMDGT//v1GxxKRMkaFmIiIiIiIOK2hQ4cCMG/ePIOTSHGyWCxER0fz1ltvce7cOZo2bcq0adOMjiUiZYgumRQREREREafm5uZGvXr12Ldvn9FRpARs27aNHj16kJWVRf/+/fn+++91eayI/CUVYiIiIiIi4tQaNGjAiRMnuHDhgtFRpITk5uYSERHBtm3bqF69Ops3b6ZWrVpGxxKRUky1uYiIiIiIOLXw8HDy8vI4ffq00VGkhHh4eLB161aee+45Tp8+Td26dZk7d67RsUSkFFMhJiIiIiIiTm3QoEEAfPPNNwYnkZI2adIkVq1ahYuLC3fddRcPPPCA0ZFEpJTSJZMiIiIiIuLUbDYbrq6uREZGEh0dbXQcuQnS09MJDw8nPj6eBg0aEBsbi6+vr9GxRKQU0QwxERERERFxahaLBT8/P3bv3m10FLlJfH19OXjwIA888ADx8fHUqFGD1atXGx1LREoR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/TVhwgRlZ2erf//++uabb5SXl6c33niDMgzABVGIAQAAAIAHxMTESJIWLVpkbpDr2Pfff69HH31U9erV0913362UlBR16NBB8+fPV3FxsVatWqWuXbuaHRPANYBbJgEAAADAA0pKSuTn56cBAwbos88+MzvOdaO8vFxz5szRP/7xDx0+fFiS1LBhQw0bNkwvv/yygoODTU4I4FpEIQYAAAAAHhIUFCQ/Pz8dO3bM7CjXvMTERMXFxWnDhg2qqKhQnTp11LdvX02fPl233Xab2fEAXOO8zQ4AAAAAANeL8PBwbd68WYZh8ETDasjJydFf/vIXffzxxzp58qQkqUOHDnr22Wc1atQofqYAPIbfJgAAAADgIVFRUTIMQykpKWZHuWYYhqE5c+aoXbt2atasmd5//33Z7XaNHj1aeXl52rdvn0aPHk0ZBsCj+I0CAAAAAB4ybNgwSVJCQoLJSa5+GzZsUFRUlHx8fPTss88qMzNTUVFRSk5OVmFhod555x01btzY7JgArlOsIQYAAAAAHuRwOBQeHq6dO3eaHeWqk5+fr0mTJikhIUE//PCDJCksLExjx47VU089JS8vL5MTArAKCjEAAAAA8KA2bdooNzdXTqfT7ChXBcMw9O6772rOnDlKT0+XJNWvX18PPfSQXnnlFTVt2tTkhACsiFsmAQAAAMCDunfvLpfLpcLCQrOjmGrz5s26++675evrq6eeekoHDhxQ7969tXbtWp04cULvvfceZRgA01CIAQAAAIAH3X///ZKkxYsXm5zkyissLNQzzzyjRo0aqUePHvryyy/VqlUrvf766yopKdG6devUt29fs2MCALdMAgAAAIAnlZSUyM/PTwMGDNBnn31mdpxaZxiG/ud//kd/+9vftHfvXklSvXr1FBsbq2nTpqlly5YmJwSAc1GIAQAAAICHNWjQQP7+/jp27JjZUWrN9u3bNXHiRH399dcqKyuTl5eXevToobi4ON11111mxwOAi/I2OwAAAAAAXG/Cw8O1efNmzZ07V5s3b1aXLl30/PPPmx2rxk6ePKkpU6ZowYIFOn78uCTphhtu0OjRo/X888/L4XCYnBAALg0zxAAAAADAQ95++20tXrxYGzduVHl5eeX2/v37a9WqVSYmqz7DMLRgwQL99a9/1e7du+V2uxUYGKhBgwZp+vTpuuGGG8yOCACXjRli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", + "image/png": "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YgAKlQceeEBnz57ViBEjdP78edWoUUNjx441OpahKlasqBIlSigkJMToKAAAAACQL1CIASiUPvnkE+3bt0+lS5fW+++/r8qVKyssLMzoWIZp0aKFoqOjFRUVZXQUAAAAADAchRiAQqtu3bo6f/68Bg8erLCwMPn5+endd981OpYhXnjhBUnSjBkzDE4CAAAAAMZjDzEANmHXrl3q3LmzYmNj9cADD2jjxo0qX7680bHuKwcHB1WpUkVHjhwxOgoAAAAAGIoZYgBsQqNGjRQVFaXHH39cx44dk6+vrz766COjY9216OhoPf/88/Lx8ZGjo6PKlCmjjh076rfffrvpa+rUqaPjx4/LarXmaTZfX19Nmzbtjl937NgxtW7dWqVLl5aTk5MqV66sN998UxkZGbkfEgAAAIBNoxADYDPMZrPmzZunrVu3ys3NTaNHj1bdunUL5L5avXv31t69ezV37lwdP35cK1euVKtWrRQbG3vT1wwYMEBWq1WLFi26q2ump6ffbdzbUqRIEQ0cOFDr16/XsWPHNG3aNH399deaMGFCnl4XAAAAgO1hySQAm2SxWNS/f38tWbJE9vb2+uSTT7L32crv4uPjVbJkSYWGhqply5a3PG/06NFasWKF0tLS1KBBA23ZskVt2rTJvuPkqlWrNHHiRB08eFCurq4KCAjQsmXLJF2b6fXss8/qxIkTWr58uXr16qU5c+Zo27ZtGjt2rHbv3i1PT08FBgZq8uTJcnFxUatWrbR58+brctzLPzOjRo3Srl27tHXr1rseAwAAAAD+FzPEANgke3t7/fTTT1q/fr2cnJz04osvqmHDhoqLizM6Wo5cXV3l6uqq5cuXKy0t7abn9enTR1FRUfrll1/0559/qlGjRjKbzfr9998lSatXr1ZgYKC6dOmivXv3KiQkRI0bN75ujA8//FD16tXT3r17NX78eJ06dUqdOnVS7969deDAAS1atEjbtm3T8OHDJUlLly5V+fLlNXHiRF28eFEXL17MHstkMmnOnDm3/T5PnjyptWvX3rL0AwAAAIC7wQwxADYvPT1dgYGBWrNmjRwcHPTFF19o0KBBRse6pSVLlmjIkCG6evWq/P391bJlS/Xv319169aVJG3btk1du3ZVVFSUHB0ds19XvHhxJSQk6ODBgxo6dKgqV66sefPm3fAavr6+atCgQfaMMUkaPHiw7Ozs9OWXX2Yf27Ztm1q2bKnk5GQ5OTnJ19dXQUFBCgoKum686tWra/LkyQoMDLzle2vWrJn27NmjtLQ0DR06VJ9//rnMZn5/AwAAACD38BMGAJvn4OCg1atXa8WKFbK3t9czzzyjhx9+WAkJCUZHu6nevXvrwoULWrlypTp16qTQ0FD5+/tnz8Dav3+/kpKS5OHhkT2jzNXVVUlJSZKkjz/+WPv27VPbtm1veZ2GDRte93j//v2aM2fOdWN27NhRVqtVYWFhtxzr6NGjOZZhkrRo0SLt2bNHP/zwg1avXq0PP/wwx9cAAAAAwJ2wNzoAAOQX3bt3V0xMjB555BGFhISodOnSmjNnjvr162d0tBtycnJS+/bt1b59e40fP16DBw/WhAkT9PTTTyspKUne3t4KDQ391+vq16+vdevWqWjRojlew8XF5brHSUlJGjZsmEaMGPGvc318fO76vfxThQoVJEk1a9ZUZmamhg4dqldeeUV2dna5Mj4AAAAAMEMMAP6haNGi2rhxY/adGPv37682bdooJSXF4GQ5q1mzppKTkyVJ/v7+ioyMlL29vfz8/K7706xZM124cEG1atXK3lz/dvn7++vw4cP/GtPPz08ODg6Srs24y8zMzJX3ZLValZGRIavVmivjAQAAAIBEIQYAN9S3b19FR0erefPm2rRpk0qVKqUVK1YYHUuSFBsbqzZt2mjevHk6cOCAwsLCtHjxYk2dOlU9evSQJLVr105NmzZVz549tX79ep05c0bbt2/XuHHjspdJVq9eXQsWLNCECRN05MgRHTx4UFOmTLnltceMGaPt27dr+PDh2rdvn06cOKEVK1Zkb6ovXdt7bMuWLTp//rxiYmKyj1evXv26/cj+1/z58/Xjjz/qyJEjOn36tH788UeNHTtW/fr1U5EiRe7lUwYAAAAA16EQA4CbcHV11datWzVnzhxZLBb17NlTXbp0UXp6uuG5mjRpouDgYLVo0UK1a9fW+PHjNWTIEM2cOVPStTs6rlmzRi1atNCgQYNUrVo19e/fX+Hh4erfv7/s7Oz0xx9/aPHixVq5cqXq16+vNm3a6I8//rjltevWravNmzfr+PHjCggIUIMGDfTWW2+pbNmy2edMnDhRZ86cUZUqVVSqVKns48eOHdOVK1duOra9vb2mTJmixo0bq27dunrnnXc0fPhwffPNN/f4GQMAAACA63GXSQC4DfHx8Wrfvr12794tV1dX/fTTT+rYsaPRse5a7dq1dezYMaWlpXEHRwAAAAA2h5+CAOA2lChRQrt27dJnn32mtLQ0derUSb169ZLFYjE62l3p06ePLBaL1qxZY3QUAAAAALjvmCEGAHcoKipK7du314EDB1S8eHGtXLlSLVq0MDrWHYmLi5OHh4e6dOmi1atXGx0HAAAAAO4rZogBwB3y8vLS/v379dFHHykpKUktW7bU448/XqDuhOju7q7SpUvrt99+MzoKAAAAANx3FGIAcJdGjRqliIgIVa9eXT/88IO8vLy0c+dOo2Pdtnbt2unKlSs6deqU0VEAAAAA4L6iEAOAe1C2bFkdOXJE7777ri5fvqyHHnpIgwcPLhCzxYKCgiRJ06ZNMzQHAAAAANxv7CEGALkkPDxcbdq00enTp1W6dGmtXbtW9evXNzrWLTk7O8vLy0tnzpwxOgoAAAAA3DfMEAOAXFKxYkWdOnVKY8eOVXR0tPz9/fXSSy/l69liDRs2VEREhFJTU42OAgAAAAD3DYUYAOSySZMm6fDhwypfvrxmzpwpHx8fHTlyxOhYNzRo0CBlZWXpm2++MToKAAAAANw3FGIAkAceeOABRUREKCgoSBcuXFCtWrU0ZswYo2P9y5NPPimTyaTvv//e6CgAAAAAcN+whxgA5LG//vpL7du3V2RkpHx9fRUSEqLKlSsbHStbtWrVWDYJAAAAwKYwQwwA8ljt2rV1/vx5DRs2TGfOnFHVqlX1zjvvGB0rW8+ePZWWlqbQ0FCjowAAAADAfcEMMQC4j3bv3q3OnTsrJiZG1apV08aNG1WhQgVDM124cEHlypVT79699dNPPxmaBQAAAADuB2aIAcB91LBhQ126dEkDBw7U8ePHValSJU2dOtXQTGXLlpW7uzszxAAAAADYDAoxALjPzGaz5s6dq61bt8rNzU1jxoxR7dq1FRkZaVimVq1aKTY2VhcuXDAsAwAAAADcLxRiAGCQ5s2bKzo6Wn369NGhQ4dUoUIFzZw505AsL774oiTpk08+MeT6AAAAAHA/sYcYAOQDISEhCgwMVGJiovz9/bVhwwa5u7vf1wyOjo6qWLGijh8/fl+vCwAAAAD3GzPEACAfaNu2rWJiYtStWzft2bNH3t7emjVr1n3NUK9ePZ06dUoWi+W+XhcAAAAA7jcKMQDIJxwcHLRq1SqtWrVKRYoU0eDBg9WsWTMlJCTcl+s/+eSTslqtmj9//n25HgAAAAAYhSWTAJAPpaamqnv37tqwYYMcHR01e/ZsDRgwIM+v6ezsrICAAG3evDlPrwUAAAAARqIQA4B87Mcff9RTTz2l1NRUtWrVSqtXr5azs3OeXc/X11dRUVFKSUnJs2sAAAAAgNFYMgkA+Vjfvn0VHR2tgIAAhYaGytPTU8uXL8+z63Xt2lVXr17V7t278+waAAAAAGA0CjEAyOdcXV21ZcsWzZ07V1arVYGBgercubPS0tJy/VqjRo2SJE2fPj3XxwYAAACA/IIlkwBQgMTHx6tjx476448/5OrqqsWLF6tTp065eo3ixYuraNGiWr16tdasWaMHH3xQXbp0ydVrAAAAAICRmCEGAAVIiRIltHPnTn3xxRdKS0tT586dFRgYKIvFcs9jJycna8WKFSpWrJguXbqkhg0b6q233tJ3332XC8kBAAAAIP+gEAOAAmjYsGG6cOGC6tWrp+XLl8vDw0ObNm26pzEbN26snj176uLFi9nH7OzsVKtWrXuNCwAAAAD5CoUYABRQnp6e2rdvn4KDg5WSkqI2bdrosccek9VqvavxBg8eLEnKzMzMPpaZmak6derkSl4AAAAAyC/YQwwACoELFy6oXbt2OnLkiNzd3bV69Wo99NBDdzzOm2++qffee++6YydPnlSVKlVyKyoAAAAAGI4ZYgBQCJQtW1aHDx/We++9pytXrqhp06Z65pln7ni22LvvvqsXX3wx+3GRIkVUqVKl3I4LAAAAAIZihhgAFDLh4eFq166dTp48KS8vL61du1YNGjSQJKWmpmrBggXq37+/ihYtesPXW61WPfbYY/rxxx/l5uamhISE+xkfAAAAAPIcM8QAoJCpWLGiTpw4oTfffFMxMTF68MEH9eKLL8pqtWr8+PF65plnNGXKlJu+3mw2a/78+SpevLgcHR0lSclpFh26cEV7Iy7r0IUrSk6797taAgAAAIBRmCEGAIXYiRMn1K5dO0VERMjT01OxsbHKysqSk5OTTp48qXLlyt30tVO//F5TlmxXrfZ9dDbuqv75j4VJko+7s1o/4KXHm/ioamm3PH8vAAAAAJBbKMQAwAaMGDFCM2bMyH5sZ2enJ554QnPmzPnXuWfjUvTGsoPaejJGZpNkvcW/EnZmkzKtWQrw89SkwDqq4O6cB+kBAAAAIHexZBIAbIDJZJLZ/P9f8jMzM/Xdd99pz5491523cFeE2gVv1vbTsZJuXYZJUuZ/T9h+Olbtgjdr4a6I3A0OAAAAAHmAGWIAUMidOnVKfn5+MplM+t8v+T4+Pjpz5oxMJpNmbjqhD9cfv+frje5QTcNbV73ncQAAAAAgr1CIAUAhd/XqVc2YMUNHjhzR8ePHderUKUVFRWWXY506dVLfsZ/onV9O5No1p/Sqo36NfHJtPAAAAADITRRiAGCDUlNTFRYWpmnTpmnBqvXyenq6LFlmWdNTlbRvrVKO71BGTISsGamyc3WXg6ePnGu0kEuN5jLZFZEkJR/ZosTdq5QeFSZJcvCqJLeG3eVSI0CO9mZtHNmSPcUAAAAA5EsUYgBg4wZ8vV07z8TralS4on+aKEt85E3P9R40XQ6lKyt+63xd+W3BDc8pHvCEPAIeU7PKHvr+2SZ5FRsAAAAA7pq90QEAAMY5cSlR209fVubVREX9OEGZCdGSJDtXdxVr0ltFSlVUVvpVpUb8paSDGyVJ6ZdO68r2RZIkk0NRubcbKkmK2/iVstKv6sq2H+RctYm2WrN0MipRfl5uxrw5AAAAALgJCjEAsGHzd0bIzmzS5T+WZpdhJkcXlXnqY9m7eWaf51ytqYo37SOZ7RS/5XspyypJKt60r1zrtpckZSbHK37zXCnLqqR961Sq0/Oa93uE3u5e6/6/MQAAAAC4BbPRAQAAxtl0LEqZ1iylHNmafaxYox7XlWF/s3MpIbuibko7dzj7mGO5Gjf8OPXcIWVas7TpeFQeJQcAAACAu0chBgA2KinNooi4FFnTr163b5hj+VvP6LJcuZT9sZ1LiX98XPxf50TEpig5zZJLiQEAAAAgd1CIAYCNCo9NVpYka1rydcft3dxv+bqsjLT/f2D3j5X3/737pCRlpade+19JZ2KvHx8AAAAAjEYhBgA2Kt1ybR8ws6PLdcctiXG3fJ2piOP/P8jMuOHHJgenf10HAAAAAPILCjEAsFEO9tf+CTA7FJV9iTLZx9POH77ZSyRJ9sVLZ3+cmRz//x8nXb7hOX9fBwAAAADyC35KAQAb5evhItN/P3auEZB9PPGP5bIkxv7r/MzkeGVeTZRj+ZrZx9LOH/n/jy8czf7Y6b/7kJn+ex0AAAAAyE/scz4FAFAYuTjay8fdWeFxKSrWuJeSD4UqMyFa1rRkRX73ioo1DlSRUr7KSr+q1IiDSjq4UWUGTJZbvY5K2rdWyrLqyo7FsnMuIZlMurJj8bWBTWa51u8oSfLxcJaLI//UAAAAAMhfTFlZWVlGhwAAGOPtlYf0/c5wZVqzlB4ToeifJl53x8n/5T1ouhxKV1b81vm68tuCG55TPOAJlXi4v5RllU/aGT1Rw1EJCQmKjY1VXFyc4uLilJmZqa+//loeHh559dYAAAAA4KYoxADAhp24lKj207ZkP7ampypp31qlHN+ujJizsmZclZ1LSRXxqCCXmi3lUrOFTP+9m2TykS1K3L1S6VFnJEkOXr5ya9hDLv9Yfnn+6+dkiT0nSbK3vzZTzGKxyGw26/z58ypT5v/3LgMAAACA+4VCDABs3JOzdmr76VhlWnPvnwM7s0lNK3vo/LzXFRoaet1zZrNZjzzyiJYvX55r1wMAAACAO8Gm+gBg4yYF1pG92ZTziXfA3mzS5MA62rBhgzp06CCz+f//ubFarVqzZo0GDBigCxcu5Op1AQAAAOB2UIgBgI2r4O6sd7rXytUxJ3avpQruzrK3t9dPP/2kmjVrys7OTpLk7u4uT09PLViwQOXKlVOdOnWYLQYAAADgvqIQAwCofyMfje5QLVfGerXDA+rXyCf7sZubm9auXatSpUpJksaPH68LFy7o999/V/PmzXX48GEFBgaqRIkSevnll5WUlJQrOQAAAFA4RUdH6/nnn5ePj48cHR1VpkwZdezYUb/99pvR0eTr66tp06bd0xgnT56Um5ubSpQokSuZcGMUYgAASdLw1lX1fq86crQ3y+4Ol1DamU1ytDdrSq86erG137+eL1eunNavX68BAwZo0KBBkqQmTZpo69atunLlil5++WVJ0vTp01W8eHEFBARo586d9/6mAAAAUOj07t1be/fu1dy5c3X8+HGtXLlSrVq1UmxsbJ5dMz09Pc/G/qeMjAw99thjCggIyPlk3BM21QcAXOdsXIreWHZQW0/GyM5suuVm+38/H+DnqUmBdVTB3fmerr18+XK99dZbOnjwoCTJ29tbI0aM0OjRo7PvUgkAAADbFR8fr5IlSyo0NFQtW7a85XmjR4/WihUrlJaWpoYNGyo4OFj16tXLPmfVqlWaOHGiDh48KFdXVwUEBGjZsmWSrs30evbZZ3XixAktX75cvXr10pw5c7Rt2zaNHTtWu3fvlqenpwIDAzV58mS5uLioVatW2rx583U57rRyGTNmjC5cuKC2bdsqKChI8fHxd/R63D5miAEArlPB3VnfP9tEG4Ja6MkmFVXRw1n/O1/MJKmih7OebFJRG0e20PfPNrnnMkySevbsqQMHDuj8+fMaMGCALl++rLFjx8rZ2Vk9e/ZUWFjYPV8DAAAABZerq6tcXV21fPlypaWl3fS8Pn36KCoqSr/88ov+/PNP+fv7q23btoqLi5MkrV69WoGBgerSpYv27t2rkJAQNW7c+LoxPvzwQ9WrV0979+7V+PHjderUKXXq1Em9e/fWgQMHtGjRIm3btk3Dhw+XJC1dulTly5fXxIkTdfHiRV28eDF7LJPJpDlz5tzyvf36669avHixPv3007v87OBOMEMMAJCjvQcPq0m7bhrwxEC9+spI+Xq4yMUx72dsWa1WffHFF5o6darCw8MlSVWrVtX48eP15JNP5vn1AQAAkP8sWbJEQ4YM0dWrV+Xv76+WLVuqf//+qlu3riRp27Zt6tq1q6KiouTo6Jj9Oj8/P7322msaOnSomjVrpsqVK2vevHk3vIavr68aNGiQPWNMkgYPHiw7Ozt9+eWX2ce2bdumli1bKjk5WU5OTvL19VVQUJCCgoKuG6969eqaPHmyAgMDb3i92NhYNWjQQPPmzVOLFi00Z84cZojlMWaIAQBy9Nn0YGVEhenXxbNVq2zx+1KGSZLZbNYLL7ygM2fO6ODBg+rQoYNOnz6tgQMHytXVVYMHD87+LR8AAABsQ+/evXXhwgWtXLlSnTp1UmhoqPz9/bNnYO3fv19JSUny8PDInlHm6uqqsLAwnTp1SpK0b98+tW3b9pbXadiw4XWP9+/frzlz5lw3ZseOHWW1WnNcyXD06NGblmGSNGTIEA0YMEAtWrS4jc8AcgOFGADgliIjI/Xdd99Jks6ePasdO3YYkqN27dpat26dUlJS9Oabb6po0aKaNWuWPD091bBhQ4WEhBiSCwAAAPefk5OT2rdvr/Hjx2v79u16+umnNWHCBElSUlKSvL29tW/fvuv+HDt2TK+++qokqWjRojlew8XF5brHSUlJGjZs2HVj7t+/XydOnFCVKlXu6f38+uuv+vDDD2Vvby97e3s9++yzunLliuzt7TV79ux7Ghs3RiEGALilSZMmKSMjQ9K1vQ/effddQ/M4ODjo3XffVXR0tDZu3KgHH3xQe/bsUbt27eTp6alx48bdt7sAAQAAIH+oWbOmkpOTJUn+/v6KjIyUvb29/Pz8rvvj6ekpSapbt+4d/0LV399fhw8f/teYfn5+cnBwkHTte9XMzMw7zr9jx47riraJEyfKzc1N+/btu+XMMtw9CjEAwE2dPXtWn3/+efbdcbKysvTLL79o7969Bie7pm3bttq1a5diYmI0ZMgQpaamatKkSXJ2dlb79u31119/GR0RAAAAuSg2NlZt2rTRvHnzdODAAYWFhWnx4sWaOnWqevToIUlq166dmjZtqp49e2r9+vU6c+aMtm/frnHjxmn37t2SpAkTJmjBggWaMGGCjhw5ooMHD2rKlCm3vPaYMWO0fft2DR8+XPv27dOJEye0YsWK7E31pWt7j23ZskXnz59XTExM9vHq1atftx/Z/6pRo4Zq166d/adcuXIym82qXbu2SpYseS+fMtwEhRgA4KYmTpwoi8Vy3TGTyaT//Oc/BiW6MXd3d3311VdKSkrSd999pypVqmjjxo2qU6eOKlasqJkzZ8pqtRodEwAAAPfI1dVVTZo0UXBwsFq0aKHatWtr/PjxGjJkiGbOnCnp2vera9asUYsWLTRo0CBVq1ZN/fv3V3h4uEqXLi1JatWqlRYvXqyVK1eqfv36atOmjf74449bXrtu3bravHmzjh8/roCAADVo0EBvvfWWypYtm33OxIkTdebMGVWpUkWlSpXKPn7s2DFduXIlDz4juFvcZRIAcFOtW7dWaGjov477+Phk3/UxvwoLC9PIkSO1Zs0aZWRkyNHRUYGBgfroo4+u+6YFAAAAgO1hhhgA4KY2bdqkzMxM/fTTT5KkTz75RHFxcTp27JjByXJWqVIlLV++XCkpKXr//ffl7u6uhQsXqly5cqpTp84tp6wDAAAAKNwoxAAAt2Q2m7M3Bi1evLhKliwpJycng1PdPnt7e40ZM0YXLlzQzp07FRAQoMOHD6tXr14qXry4Xn75ZSUlJRkdEwAAAMB9RCEGAMjR33dtLFKkiMFJ7k3jxo21ZcsWJSQkKCgoSCaTSdOnT1fx4sXVvHlz/f7770ZHBAAAAHAfUIgBAHKUkZEhSXJ0dDQ4Se5wcXFRcHCw4uPjtWLFCtWqVUu//fabmjZtKm9vb02ePPlfNxMAAAAAUHhQiAEAcvT3DDEHBweDk+S+7t2768CBA7pw4YIGDBig+Ph4vfHGG3J2dlaPHj106tQpoyMCAAAAyGUUYgCAHBXmQuxv3t7emj9/vpKTk/Xpp5+qbNmyWrlypfz8/FS1alXNnTvX6IgAAAAAcgmFGAAgR7ZQiP3NbDbrhRde0JkzZ3Tw4EF17NhRYWFhevrpp+Xi4qJnn31WcXFxRscEAAAAcA8oxAAAOSpse4jdrtq1a2vt2rVKSUnR+PHj5ezsrNmzZ8vT01MNGzbUxo0bjY4IAAAA4C5QiAEAcmRLM8RuxMHBQRMnTlR0dLRCQkLUsGFD7dmzR+3bt5enp6feeOMNpaamGh0TAAAAwG2iEAMA5OjvOy7a2gyxG2nTpo3++OMPxcTEaMiQIUpNTdXkyZPl6uqq9u3b6+DBg0ZHBAAAAJADCjEAQI5sdcnkrbi7u+urr75SUlKS5s2bpypVqmjjxo2qW7euKlasqBkzZshqtRodEwAAAMANUIgBAHL095JJCrEbe/zxx3Xs2DGdPn1aPXv21MWLFzVixAg5Ozurf//+On/+vNERAQAAAPwDhRgAIEd/L5m01T3EblelSpW0bNkyXb16VVOmTJGHh4cWLVqk8uXLq3bt2lq6dKnREQEAAACIQgwAcBv+XjLp5ORkcJKCwc7OTq+99prOnz+vP/74Qy1atNCRI0fUu3dvFS9eXCNGjFBSUpLRMQEAAACbRSEGAMgRe4jdvUaNGmnz5s1KTEzUyJEjZTabNWPGDBUrVkzNmzfXjh07jI4IAAAA2BwKMQBAjlgyee+cnZ318ccf6/Lly1qxYoXq1Kmj3377Tc2aNZO3t7cmTZqU/XkGAAAAkLcoxAAAOfp7hpi9vb3BSQqH7t27a//+/bp48aIef/xxxcfHa9y4cXJ2dlb37t116tQpoyMCAAAAhRqFGAAgR8xcyhtlypTRvHnzlJycrM8//1zlypXTqlWr5Ofnp6pVq2ru3LlGRwQAAAAKJQoxAECO/p4hhrxhNpv13HPPKSwsTH/99Zc6duyosLAwPf3003JxcdEzzzyjmJgYo2MCAAAAhQaFGAAgR5mZmUZHsBm1atXS2rVrlZKSovHjx8vFxUXffvutvLy89OCDD2r9+vVGRwQAAAAKPAoxAECOMjIyZDKZjI5hUxwcHDRx4kRFRUXp119/VaNGjbR371517NhRnp6eGjt2rFJTU42OCQAAABRIFGIAgByxh5ixWrdurZ07dyo2NlZDhw5VWlqa3n//fbm6uqpdu3Y6cOCA0REBAACAAoVCDACQo8zMTGaI5QMlS5bUl19+qcTERM2bN09+fn4KCQlRvXr15OPjoxkzZshqtRodEwAAAMj3KMQAADmyWCwUYvnM448/rqNHjyosLEyBgYGKjIzUiBEj5OzsrP79++vcuXNGRwQAAADyLQoxAECOWDKZf/n6+mrp0qVKTU3V1KlT5eHhoUWLFqlChQqqVauWlixZYnREAAAAIN+hEAMA5Iglk/mf2WzWq6++qvPnz+uPP/5QixYtdPToUT366KMqXry4XnrpJSUlJRkdEwAAAMgXKMQAADliyWTB0qhRI23evFmJiYkaNWqUzGazZs6cqWLFiunhhx/Wjh07jI4IAAAAGIpCDACQo8zMTJnN/JNR0Dg7O+ujjz7S5cuXtWLFCtWpU0fbt29Xs2bN5O3trffee4/lsAAAALBJ/HQDAMgRSyYLvu7du2v//v26ePGiHn/8ccXHx+vNN99U0aJF9cgjj+jEiRNGRwQAAADuGwoxAECOKMQKjzJlymjevHlKTk7Wl19+qfLly+vnn39WtWrV5Ofnpzlz5igrK8vomAAAAPlGcppFhy5c0d6Iyzp04YqS05hhXxiYsviuFwCQgxo1aujs2bNsyl5IHT58WK+88oo2bNigzMxMOTs7q1+/fpo6dao8PT2NjgcAAHDfnbiUqPk7I7TpWJQi4lL0z+LEJMnH3VmtH/DS4018VLW0m1ExcQ8oxAAAOapWrZouXryoxMREo6MgD6Wnp+u9997T559/rujoaJlMJjVo0ECTJk1Sx44djY4HAACQ587GpeiNZQe19WSM7MwmZVpvXpn8/XyAn6cmBdZRBXfn+5gU94pCDACQoypVqigmJkZXrlwxOgruk9DQUI0ZM0a7du1SVlaW3N3dNXToUE2YMEFOTk5GxwMAAMh1C3dFaMLKQ7JYs25ZhP0vO7NJ9maT3uleS/0b+eRhQuQm9hADAOSIu0zanlatWmnnzp2Ki4vTsGHDlJ6ervfff1+urq5q27at9u/fb3REAACAXDNz0wm9vvSg0izWOyrDJCnTmqU0i1WvLz2omZu4UVFBwU83AIAcWa1WCjEbVaJECX3xxRdKTEzU/Pnz5efnp19//VX169eXj4+PPvnkE1mtVqNjAgAA3LWFuyL04frjuTLWh+uPa9GuiFwZC3mLJZMAgByVL19eaWlpio6ONjoK8oHw8HCNHDlSP//8szIyMuTo6Kju3bvr448/Vvny5Y2OBwAAcNvOxqWofs8hitv6w/VPmMwyF3WTQ6mKcqnTXq61W2c/lfTXr0o9s0/pkSeVmRgrqyVN9m6lVLRKQxV/uL+ci5XUxpEt2VMsn+PX/QCAHLFkEv9UsWJFLV26VKmpqfrggw/k6empxYsXq0KFCqpVq5aWLFlidEQAAIDb8sayg7rhCsksq6wpV5QafkCxP3+kKzuXZj8V+8sMJf/1qzJiImRNS5YyLbLEX1Tin6sUOXeU0pIT9Mayg/fvTeCu8NMNACBHLJnEjZjNZo0ePVrnzp3Trl271LJlSx09elSPPvqoihcvrpdeekkJCQlGxwQAALihE5cStfVkjP65cM6p8oMq/fgUefX/j4pWa5p9PPHPn7M/NplMcixfU+4dX5RX//+oeMATkp29JMly5ZLi/1ihrSdjdDKKO7TnZ/x0AwDIEYUYctKwYUOFhoYqMTFRo0aNktls1syZM1WiRAk9/PDD+u2334yOCAAAcJ35OyNkZzZdd8zOuYScKtRSUd/6KhHwRPbxzOTL2R+X6v2myjwxVW4NOl877+H+cmvQNfv5tIvHZWc2ad7v7CWWn/HTDQAgR1arVXZ2dkbHQAHg7Oysjz76SJcvX9aqVatUt25dbd++Xc2bN1eZMmX03nvvyWKxGB0TAABAm45F3fSOklmZGbp64vfsxw6lKmZ/XLSS/7/OL+JeNvtjcxEnZVqztOl4VC6mRW6jEAMA5IgZYrgb3bp10759+xQZGaknnnhCV65c0ZtvvqmiRYuqW7duOnGC25IDAABjJKVZFBGX8q/jyX+FKPz9bor4IFDxW76XJJmdi6tku2G3HC/l2Pbsj4tWflCSFBGbouQ0fhGYX/HTDQAgR8wQw70oXbq0vv/+eyUnJ+vLL79UhQoVtHr1alWrVk1+fn769ttvZbVajY4JAABsSHhssm48N+zfTPYOykr/d3n2t8tbvldq+H5JkkPZB+RSp60kKUvSmdjke0yKvEIhBgDIEYUYcoPZbNbQoUN1+vRpHT58WJ06dVJ4eLieeeYZubm5adCgQYqJiTE6JgAAKCSysrKUlJR0w+fSLTf+Zdzfm+qXfmySigc8LsmkzIRoRS+dpMyky/86//Kvs5SwfZEkyd6jvLwefUsm8/9/33yz68B4FGIAgBxlZWVRiCFX1ahRQ7/88ouSk5M1YcIEubi4aM6cOfLy8pK/v7/WrVtndEQAAFDATZs2TW5ubvLz89Mzzzyjb775RocPH5bVapWD/Y3rkL831XeqWFclHn5MTpWv7ReWZUlTysmd2edlZVkVu3amEv5YJkkqUspXZQZMlp1z8evGu9l1YDz+ZgAAOWKGGPKKg4OD3n77bUVFRSk0NFSNGzfWvn371KlTJ3l4eOj1119Xamqq0TEBAEAB5O3tLUk6deqUvv/+ew0ZMkS1atWSvb29Gj5QUVlZt7Fo8h/nWK8mXjtkzVTMqo+UtG+tpGvLJEs//r7sXEpe91KTJF8Pl9x5M8h1FGIAgBwxQwz3Q8uWLfX7778rLi5Ow4YNU3p6uqZMmSJXV1e1bdtW+/btMzoiAAAoQJycnLI//uddrrOysuTlXlyeTv8uxDJT4pV69pBSww/oyvYflXpmX/ZzRdzLSZKil05SyuHNkiS7YqVUovkAZUSHX3vd2UNKjzojSfLxcJaLo30evDPkBlPWbVWiAABb5uTkpJo1a2rPnj1GR4GNWbBggSZOnKijR49KkipUqKBXXnlFL730Enc+BQAA2axWq0JDQ/X9999ry5YtCg8PV2Zm5g3PDQ4O1ssvv6x3Vh3W9zvDFbt5nq78tuCW4zuUrqIyAz+Syc5e4e93u+W5jhVqq9yTU/Rkk4p6u3utu35PyFt8JwkAyBFLJmGUxx57TEeOHNGZM2fUq1cvXbp0SUFBQXJ2dlbfvn119uxZoyMCAAADWK1WbdiwQU899ZQqVaokBwcHtW3bVnPmzNHFixdVr149vfHGG+rbt6/s7OxkZ2cnJycnrVixQkFBQTKZTHq8iY8yrTefI2Syd1SRUhVVrFk/lR4wWSa725/tlWnN0hMP+eTGW0UeYYYYACBHRYoUUcOGDbVjxw6jo8DGWa1WBQcHKzg4WOfPn5d0bYP+d955R3369DE4HQAAyCtWq1Xr1q3TvHnz9Ntvv+ns2bOyWq/dwdHZ2Vk1a9ZU586d9eyzz6pixYrZr5s/f76eeOIJeXl56ZdffpG/v/914z45a6e2n469ZTF2p+zMJjWr7KHvn22Sa2Mi91GIAQByZG9vr4ceekjbtm0zOgqQbffu3Ro9erS2bt0qq9UqNzc3Pfnkk5o8ebKKFStmdDwAAHAPrFarVq9erR9++EG//fabzp8/n12Aubi4qFatWurSpYueeeYZVahQ4abjJCYm6r333tPw4cNVvnz5fz1/Ni5F7YI3K81izbXsjvZmbRzZUhXcnXNtTOQ+CjEAQI7s7e3VvHlzhYaGGh0F+JeUlBS99dZbmj17ti5fviyTyaSHHnpIH3zwgR5++GGj4wEAgNtgsVi0atUqLViwQNu3b9eFCxey7wLp6uqq2rVrq1u3bho0aJDKli2bq9deuCtCry89mGvjTelVR/0asVwyv6MQAwDkyM7OTq1atVJISIjRUYBb+vnnn/Xmm29q//79kqTSpUvrxRdf1NixY2Vvz12eAADILywWi5YvX66FCxdqx44dunjxYnYB5ubmpjp16uiRRx7RoEGDVLp06TzPM3PTCX24/vg9j/Nqhwf0Ymu/XEiEvEYhBgDIkdlsVvv27bVu3TqjowC3JSoqSqNHj9ZPP/2kq1evyt7eXh07dtTHH3+satWqGR0PAACbY7FYtGTJEi1cuFA7d+5UZGRkdgFWrFgx1a1bV927d9egQYPk6elpSMaFuyI0YeUhWaxZd7SnmJ3ZJHuzSRO712JmWAFCIQYAyJHZbFbnzp21evVqo6MAd8RqtWrWrFmaPHmywsLCJEmVK1fWG2+8oUGDBsls5obbAADkhfT0dP34449avHix/vjjD126dCm7ACtevLjq16+vHj166KmnnpK7u7vBaf/f2bgUvbHsoLaejJGd2XTLYuzv5wP8PDUpsA57hhUwFGIAgByZTCZ1795dK1asMDoKcNeOHDmiV155RRs2bJDFYpGzs7P69OmjDz/80LDfRAMAUFikpqZq0aJFWrx4sXbt2qWoqKjs50qWLKn69esrMDBQTz75pEqUKGFc0Nt04lKi5u+M0KbjUYqITdE/ixOTJB8PZ7Wu5qUnHvKRn5ebUTFxDyjEAAA5MplM6t27t3766SejowD3LD09XZMnT9Znn32W/c16gwYNNGnSJHXq1MngdAAAFAwpKSlasGCBlixZot27dys6Ojr7OXd3dzVo0EC9evXSE088UeDv/pycZtHQV8bpp6XL1bVzR33/2cdycWRv0oKOQgwAkCOTyaR+/fpp4cKFRkcBctWWLVv02muv6Y8//lBWVpbc3d01ePBgvfPOO3JycjI6HgAA+UZycrLmz5+vJUuW6M8//1RsbGz2cx4eHnrwwQfVq1cvPf7443J1dTUwae6zWCwqU6aMYmNj5erqqqioKBUtWtToWLhHbJwBALgt3KEPhVGLFi30+++/6/Lly3ruueeUkZGhqVOnysXFRW3atNG+ffuMjggAgCESExP12WefqX379vLw8JCrq6uGDRum9evXy2QyqVOnTvr666+VnJysmJgYrVu3TsOGDSt0ZZgk/fDDD9kFYFJSkj7//HODEyE3MEMMAHBLFotFRYoU0dNPP61vv/3W6DhAnlu0aJHefvttHT16VJJUvnx5vfLKKxoxYgSb8AMACq2EhATNnTtXy5cv1969e3X58uXs57y8vNSwYUP16dNH/fv3t6lZ1JmZmapWrZpOnz6dfaxkyZKKiIgolOWfLeG7OgDALaWnp0tihhhsR79+/XTkyBGFh4erd+/eioqK0siRI1W0aFH16dNHERERRkcEAOCexcfHa9q0aWrVqpVKliyp4sWLa8SIEdq0aZMcHBz0yCOP6Pvvv1daWpouXbqk1atX6+mnn7apMkySFi9efF0ZJl373M2cOdOgRMgtzBADANzS5cuX5e7urueff16fffaZ0XGA+85qtWratGn6+OOPdf78eUlSjRo19Pbbb6tv374GpwMA4PbExMRozpw5WrlypQ4cOKArV65IurZXbOnSpfXQQw+pb9++6t27txwcHAxOm3889NBD2rlzp+zs7JSZmSmz2Syr1SovLy9dunTJ6Hi4BxRiAIBbioyMlLe3t0aMGKFPPvnE6DiAof7880+NHj1aW7ZskdVqlZubm5588klNnjy5wN9BCwBQuERFRWn27Nn6+eefdeDAASUmJkq6VoB5e3vroYceUv/+/RUYGMhKgFtYsmSJ9u3bp7Nnz2ru3Lnq0KGDWrZsqSpVqqhfv35Gx8M9oBADANxSeHi4fH19NWrUKH300UdGxwHyhZSUFE2YMEGzZs3S5cuXZTKZ1KRJE33wwQdq3ry50fEAADYoMjJSs2bN0urVq3Xw4EElJSVJulaAlS1bVk2bNtWAAQP0yCOPUIDdhe3bt+vhhx/WpEmTNHbsWKPjIBfwXwEA4JbS0tIkianzwD84Ozvrgw8+0AcffKA1a9Zo3Lhx+v333xUQECAvLy+9+OKLeuONN/iBAwCQZ86dO6fZs2dr9erVOnTokJKTkyVJZrNZ5cqVU9euXTVgwAB169aNm8LkgiJFiki6tsk+Cgf+qwAA3NLfm+r//U0AgOt16dJFe/fu1aVLlzRw4EAlJiZqwoQJKlq0qLp27apjx44ZHREAUAhERETorbfeUqNGjeTi4qIKFSpowoQJ2r17tzw8PDRgwACtXr1aGRkZioiI0MKFC9W9e3fKsFzy9y+5KMQKD/7LAADcUmpqqiTuMgnkxMvLS3PnzlVSUpK+/vpr+fj4aM2aNapevbqqVKmiWbNmyWq1Gh0TAFBAhIWFady4cWrYsKGcnZ1VsWJFvfvuu9qzZ4+8vLz05JNPav369crIyFB4eLjmz5+vLl26UIDlEQqxwof/UgAAt/T3DDGWTAK3x2w2a/DgwTp16pSOHDmiLl26KCIiQoMHD5arq6ueeuopRUdHGx0TAJDPnDx5UmPHjlWDBg3k7OysypUra9KkSdq3b5+8vb319NNPKyQkRBkZGQoLC9N3332n9u3bU4DdJ38XYhaLxeAkyC38uh8AcEssmQTuXvXq1bOXr0yePFmffvqpvvvuO3333XeqX7++Jk2apM6dOxsdEwBggKNHj2rWrFlav369jh8/ft2s/IoVK6pVq1YaOHCgmjdvTumVD7CHWOFDIQYAuCU21QfuXZEiRfTWW2/prbfe0pYtWzRmzBjt3LlTXbp0UcmSJTV48GBNnDhRTk5ORkcFAOSRQ4cOadasWdq4caOOHz+e/T2Wvb29KlWqpFatWumpp57Sww8/bHBS3AhLJgsfamYAwC2xZBLIXS1atNCOHTsUHx+v559/XhaLRR988IFcXFzUunVr7d271+iIAIBccODAAb388suqXbu2HB0dVbt2bQUHB+vo0aPy9fXVc889p99//10ZGRk6fvy4vvrqK8qwfIxCrPChEAMA3BKFGJA3ihUrps8++0wJCQlauHChqlWrptDQUPn7+6tChQoKDg5mE34AKED27Nmjl156STVq1JCjo6Pq1aun6dOn68SJE6pcubKGDx+uXbt2KT09XUePHtXnn3+uJk2aGB0bt4klk4UPhRgA4JbYQwzIe/369dORI0cUHh6uRx99VFFRURo1apSKFi2qRx99VBEREUZHBAD8j127dumFF15Q9erV5eDgoAcffFAzZ87U6dOn5efnpxEjRmjv3r1KS0vTkSNHNGPGDDVs2NDo2LhLbKpf+LCHGADglpghBtw/Pj4+Wrx4saxWq6ZPn66PPvpIS5Ys0ZIlS1S9enW9/fbb6tevn9ExAcAmbd++XXPmzFFoaKjCwsKyixFHR0dVr15d7dq10zPPPKPatWsbnBR54e9fDjN7u/BghhgA4JYyMjIkXftmD8D9YTabFRQUpLNnz+rPP/9U69atdfz4cfXv31/FihXT888/rytXrhgdEwAKraysLG3dulWDBw+Wn5+fihQpoocfflhff/21zp49q5o1a2r06NE6fPiwUlNTdeDAAX388ceUYYUYSyYLHwoxAMAtMUMMMJa/v79+/fVXJSYm6tVXX5W9vb2++OILlSxZUk2bNtXWrVuNjggABZ7VatWvv/6qQYMGqUqVKipSpIhatGihWbNm6cKFC6pdu7Zef/11HT9+XFevXtX+/fv1wQcfqEaNGkZHx33CksnChyWTAIBbohAD8gdnZ2dNnTpVU6dO1Zo1azRu3Dj9/vvvatGihby8vPTiiy9q7Nix7PcHALfBarUqJCRE33//vbZu3aqzZ89mz/wpWrSo6tWrp06dOmnw4MGqVKmSwWmRH9jZ2UliyWRhwgwxAMAtsWQSyH+6dOmivXv36tKlSxo4cKASExM1YcIEOTs7q0uXLv/H3n1HR1VvbRz/zqSShBBCCy0JvbfQpPfeiTRRBGnqVQEpSlEQEAFp9quCgog06VUERZoiLQTpHakhCSG9TGbeP7iM5iXBQsJJeT5ruW5y5sw5zwnrZjJ79v4dTp8+bXTEHOf27du88MIL+Pr64uLigo+PD23atGHv3r1GR8Pf35958+b94+fFx8fTv39/qlSpgqOjI127dk33bCKPi9VqZcuWLfTt2xd/f3+cnJxo3bo1ixcvJiQkhBo1avDGG29w6dIlYmNjOXToEG+//baKYWJnNt8rn2hkMvtQh5iIiDyUOsREMq+CBQuyaNEivvzyS7788kumTZvGli1b2LJlCyVLlmTs2LE899xz9j/iJeMEBgaSmJjIokWLKFmyJLdu3WLHjh2EhYVl2DkTExMz9HdzcnIyuXLl4pVXXmHVqlUZdh6RjGC1Wtm4cSNLly5l7969XLt2zd7Z4+7uTq1atWjfvj0DBw6kWLFiBqeVrEQFsexDfx2JiMhD3e8QU0FMJPMym80MHDiQ8+fPc+rUKdq3b8+VK1cYPHgwHh4e9OvXj5CQEKNjZlsRERHs3r2bGTNm0KxZM/z8/KhTpw5jx46lc+fOKfYbNGgQBQoUwNPTk+bNm3P06NEUx9qwYQO1a9fG1dWV/Pnz061bN/tj/v7+TJkyhX79+uHp6cmQIUMA2LNnD40aNSJXrlwUL16cV155hZiYGACaNm3K5cuXGTFiBCaTCZPJ9Levy93dnU8++YTBgwfj4+PzKD8ikQxnsVhYs2YNPXv2pFixYjg6OtKlSxeWLVvGnTt3qFOnDlOnTuXatWtER0ezf/9+Jk6cqGKY/GMqiGUfKoiJiMhDaWRSJGspV64cmzZtIi4ujrfeeovcuXOzePFiChUqRI0aNdiyZYvREbMdDw8PPDw8WLt2LQkJCWnu16NHD0JCQtiyZQuHDh0iICCAFi1aEB4eDsCmTZvo1q2bfSR2x44d1KlTJ8UxZs2aRbVq1Thy5AhvvPEG58+fp23btgQGBhIcHMzy5cvZs2cPL730EgCrV6+mWLFiTJ48mRs3bnDjxg37sUwmEwsXLkz/H4jIY2CxWPj2228JDAykaNGiODs70717d1auXElkZCT16tXjnXfe4datW0RFRfHzzz8zfvx4ihQpYnR0yeK0hlj2YbLZbDajQ4iISOY1ZswY3n33Xc6fP0/JkiWNjiMi/8KePXsYPXo0+/fvx2azkTdvXgYOHMjkyZPJlSuX0fGyhVWrVjF48GDi4uIICAigSZMm9O7dm6pVqwL3/g06dOhASEhIig8YSpcuzZgxYxgyZAj169enZMmSfP3116mew9/fnxo1arBmzRr7tkGDBuHg4MCnn35q37Znzx6aNGlCTEwMrq6u+Pv7M3z4cIYPH57ieOXLl+edd95J0YWWlv79+xMREcHatWv/wU9FJP0kJSWxatUqli9fzv79+7l58yb338p6enpSrVo1OnXqxIABA8ifP7/BaSW7MplMdO3aNcXvYcm61CEmIiIPpZFJkayvYcOG/Pzzz0RERPDCCy9gsViYNWsWHh4eNGvWjMOHDxsdMcsLDAzk+vXrrF+/nrZt27Jz504CAgLsHVhHjx4lOjqafPny2TvKPDw8uHjxIufPnwcgKCiIFi1aPPQ8tWrVSvH90aNHWbhwYYpjtmnTBqvVysWLFx96rFOnTv2tYpiIERITE/n666/p0qULPj4+uLi40KdPH9auXUtcXByNGzdm7ty5hIWFcffuXXbt2sXo0aNVDJMMpw6x7EOL6ouIyEPdL4i5uroanEREHpWnpycff/wxH3/8MStWrGDSpEns3LmTmjVrUrRoUV599VWGDx+uRfj/JVdXV1q1akWrVq144403GDRoEBMnTqR///5ER0dTuHBhdu7c+cDzvLy8AP5Wt567u3uK76Ojoxk6dCivvPLKA/v6+vr+q+sQMUJ8fDzLly9nxYoVHDx4MMW6h3nz5qVp06Z069aNZ555xv7/GREjaA2x7EMFMREReSiLxQKoICaS3fTs2ZOePXty5coVRo4cyfr16xk5ciRjx46lU6dOzJ49Gz8/P6NjZmkVK1a0jxgGBARw8+ZNHB0d8ff3T3X/qlWrsmPHDgYMGPC3zxEQEMCJEycoXbp0mvs4OzvrDZxkOrGxsSxdupRvv/2WQ4cOcfv2bftj3t7etGzZkm7duvH000/j6elpYFKRP5hMJv0+zUb08Z+IiDxUYmIioJFJkezK19eXlStXEhcXx9y5cylYsCCrVq3C39+fChUqsHz5cqMjZnphYWE0b96cr7/+muDgYC5evMjKlSuZOXMmXbp0AaBly5bUq1ePrl27sm3bNi5dusS+ffsYP348Bw8eBGDixIksXbqUiRMncvLkSY4dO8aMGTMeeu7XXnuNffv28dJLLxEUFMTZs2dZt26dfVF9uLf22K5du7h27RqhoaH27eXLl//LdXBOnDhBUFAQ4eHh3L17l6CgIIKCgv7lT0pysujoaD799FPatGlD/vz5cXd3Z9CgQWzduhWr1Urr1q3573//S1RUFGFhYXz//fe8+OKLKoZJpqORyexDHWIiIvJQ9zvEHB31kiGSnZnNZvvC64cPH2bUqFH89NNP9O7dm8GDB9O3b1/eeecdjSqlwsPDg7p16zJ37lzOnz9PUlISxYsXZ/DgwYwbNw6411WwefNmxo8fz4ABA7h9+zY+Pj40btyYQoUKAdC0aVNWrlzJlClTmD59Op6enjRu3Pih565atSo//fQT48ePp1GjRthsNkqVKkWvXr3s+0yePJmhQ4dSqlQpEhIS7AuRnz59mrt37z70+O3bt+fy5cv272vUqAGA7sslfyUyMpKvv/6aNWvWcPjwYfvdVAHy589P27ZtefLJJ+nTpw9ubm4GJhX5+0wmkwpi2YjuMikiIg/11FNPsXTpUr35EcmB4uPjmThxIvPnzyc8PByTyUSdOnWYOXPmXxZqRCRniYiIYPHixaxZs4agoCDu3Lljf6xgwYLUqlWLnj170qtXLy3DIFmWg4MDTZo04YcffjA6iqQDFcREROShevTowbfffquCmEgOt2XLFsaPH8+RI0eAe29wX3jhBcaNG6eRapEcKDw8nK+++oq1a9dy9OhRIiIigHsdNAULFqRu3br06NGDnj176neEZBsODg40atQo1RukSNajgpiIiDxUt27dWLt2rQpiIgJASEgIY8aMYcWKFcTFxeHo6EirVq2YM2cO5cuXNzqeiGSQ0NBQvvzySzZs2MDRo0eJjIwE7hXAfHx8qFu3Lj179iQwMFAFMMm2HB0dqV+/Prt27TI6iqQDFcREROShOnXqxMaNG1UQE5EUrFYrX375JdOmTePChQsAlChRgrFjxzJw4EDMZt27SSQrCwkJ4YsvvmDDhg0cO3aMqKgo4F4BrHDhwjzxxBP06dOHrl27ap1RyTGcnJyoU6cOe/fuNTqKpAMVxERE5KHatWvHd999pwVERSRNp0+f5tVXX2Xbtm1YLBZy5crFk08+yaxZsyhYsKDR8UTkb7h58yYLFixg48aN/Pbbb0RHRwP3CmBFihShXr16PPXUU3Tq1EkFMMmxnJycqFWrFj///LPRUSQdqCAmIiIP1apVK3bs2KGCmIj8JYvFwvTp0/nwww+5desWANWqVWPatGm0b9/e4HQi8mdXr15lwYIFbN68mePHjxMTEwPcu+Ns0aJFadCgAX369KFjx47q+BT5H2dnZ2rUqMH+/fuNjiLpQAUxERF5qObNm/PTTz+RnJxsdBQRyUL27NnDmDFj+OWXX7DZbHh5eTFw4EAmT56Mm5ub0fFEcpzLly+zYMECtmzZwokTJ4iNjQXuFcCKFy9OgwYNePrpp2nTpo0KYCJpcHFxoUqVKhw8eNDoKJIOVBATEZGHaty4Mfv27cNisRgdRUSyoMjISMaOHcvixYuJiorCbDbTqFEjZs+eTc2aNY2OJ5JtXbx4kfnz57N161ZOnjxJXFwccK8A5uvrS6NGjXjmmWdo0aKFCmAif5OLiwuVK1fm0KFDRkeRdKCCmIiIPFT9+vU5cOAASUlJRkcRkSxuxYoVTJo0iZMnTwJQtGhRRowYwYgRI/SGXOQRnT17lgULFvDdd99x+vRpewHMwcEBPz8/GjduzDPPPEPTpk31/zeRf8nV1ZUKFSpw5MgRo6NIOlBBTEREHqpu3bocOXKExMREo6OISDZx5coVRo4cyYYNG0hISMDZ2ZmOHTsyZ84c/Pz8jI4nkiWcPHmSL774gm3btnHmzBni4+MBcHR0xM/Pj6ZNm9KvXz8aNmyoAphIOsmVKxdlypQhODjY6CiSDlQQExGRh6pVqxbHjh0jISHB6Cgiks1YrVY++OADZs+eze+//w5A+fLlmThxIr179zY4nUjmcvz4cRYsWMD27ds5c+aM/XXZ0dGREiVK0LRpU5599lkaNGhgcFKR7MvNzY1SpUpx7Ngxo6NIOlBBTEREHqp69eqcOnXK/smziEhGCAoKYuTIkezcuROr1YqHhwd9+/Zl+vTpeHl5GR1P5LELDg62F8DOnTtn79R2cnKiZMmSNGvWjAEDBlCnTh2Dk4rkHO7u7vj7+3P8+HGjo0g6UEFMREQeqkqVKpw/f95+NyoRkYwUHx/PxIkTmT9/PuHh4ZhMJurUqcOMGTNo0qSJ0fFEMsyhQ4f48ssv2bFjBxcuXLAXwJydnSlZsiQtW7ZkwIABBAQEGJxUJOfy8PCgePHi9rUwJWtTQUxERB6qYsWKXL58mZiYGKOjiEgOs3XrVsaNG2dfvLhAgQK8+OKLjBs3DmdnZ4PTiTya/fv3s3DhQn788UcuXLhgv3mNs7MzZcqUoUWLFgwYMIDq1asbG1RE7Dw8PChatCinT582OoqkAxXERETkocqVK8e1a9eIjo42OoqI5FChoaGMHj2a5cuXExcXh6OjIy1btmTOnDlUqFDB6Hgif8vevXtZtGgRO3fu5OLFi1gsFgBcXFwoW7YsLVu25LnnnqNy5coGJxWRtHh6elKoUCHOnj1rdBRJByqIiYjIQ5UuXZqQkBAiIyONjiIiOZzVamXhwoVMmzaN8+fPA+Dv78/YsWMZNGiQ7qQnmYbVamXPnj189dVX7Ny5k8uXL9sLYK6urpQtW5bWrVvz3HPPqagrkoXkyZOH/Pnz21+DJGtTQUxERB6qZMmShIeHExERYXQUERG7s2fPMmLECL777jssFguurq48+eSTzJo1i0KFChkdT3IYq9XKzp07+eqrr9i9ezeXL18mOTkZgFy5clGuXDnatm3Lc889R5kyZQxOKyL/lpeXF97e3ly4cMHoKJIOVBATEZGH8vPzIyoqivDwcKOjiIg8wGKxMH36dD788ENu3boFQNWqVZk2bRodOnQwOJ1kV1arle3bt/P111+ze/durly5gtVqBe4VwCpWrEibNm0YNGgQJUqUMDitiKSXvHnzkidPHi5dumR0FEkHKoiJiMhDFS9enLi4OEJDQ42OIiLyUHv27OG1117j559/xmaz4eXlxXPPPceUKVNwc3MzOp5kYVarla1bt7JkyRL27NnD1atX7QUwd3d3KlSoQLt27Rg4cCB+fn4GpxWRjOLt7Y2HhwdXrlwxOoqkAxXERETkoYoUKYLFYiEkJMToKCIif0tkZCTjx4/nq6++IjIyErPZTMOGDZk9eza1atUyOp5kAVarlY0bN/LNN9+wb98+rl27lqIAVqlSJTp06MBzzz1HsWLFDE4rIo9Lvnz5cHNz4/fffzc6iqQDFcREROShfHx8ALh586bBSURE/rlvv/2WiRMncuLECeBekX/EiBG8+uqrWoRf7CwWC+vXr2fp0qX8/PPPXL9+nftvkzw8PKhSpQodOnRgwIABFClSxOC0ImKUAgUK4OzszLVr14yOIulABTEREXmoggUL4ujoyPXr142OIiLyr129epVXX32V9evXk5CQgLOzMx06dGDu3LkaccuBLBYLq1evZvny5fzyyy/cuHHDXgDLnTs3VatWpWPHjjz33HMULFjQ4LQiklno7+LsRQUxERF5qPz585MrVy61hotItmC1Wvnwww+ZNWuW/fdauXLlePPNN3nqqacMTicZJTExkVWrVrF8+XL279/PrVu37AUwT09PqlWrRufOnenfvz/58+c3OK2IZFaFChXCbDZz48YNo6NIOlBBTEREHsrb2xtPT0/dTUdEsp2jR4/y6quv8tNPP5GcnIyHhwdPPfUUM2bMwMvLy+h48ggSExNZsWIFy5cv58CBA/Y7kAJ4eXlRrVo1unbtSr9+/fD29jYwqYhkJYULF8Zqtab4nSJZlwpiIiLyUF5eXnh7e3PhwgWjo4iIZIj4+HgmTZrE559/Tnh4OCaTidq1azNjxgyaNm1qdDz5G+Lj41m2bBkrV67kwIED3L592/5Y3rx5qV69Ot26deOZZ55RsVNE/jXdbCp7UUFMREQeytPTk0KFCnH27Fmjo4iIZLjvvvuOcePG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gbtalihRwt5x07ZtW41aSo6yYMECBg0axLfffktgYKDRceQR6beXiIikKTk5WSOTIpKuli9fzptvvsmZM2cAKFmyJBMmTPhba7eISMYym83Uq1ePevXq2bfFxsaybt06NmzYwIEDBzh37hwnTpxgwYIFAHh7e1O+fHmaNWtG7969qVy5slHxRTLc/QKwpiiyBxXEREQkTVpDTETSQ2xsLGPHjmXhwoVERkbi4OBAy5YtmTdvHpUqVTI6nog8hJubG3369KFPnz72bVeuXGH58uV8//33HDt2zD5q+fbbb+Pg4ECRIkWoUaMG7dq1o2fPnnh7ext4BSLpRwWx7EWL6ouISJr0Yi8ijyI4OJjmzZuTO3du3n//fUwmEyNHjiQ6Oprvv/9exTCRLMrX15fRo0ezbds2bty4QVJSEnv27GH48OFUr16diIgI1q9fzwsvvEC+fPlwc3OjcuXKDB48mE2bNunvC8my7hfEtJxI9qAOMRERSVNycjJmsz47EZG/z2q1smDBAt5++20uX74M3Luj1tSpU7Xeikg2ZTabadCgAQ0aNLBvi42NZc2aNWzcuJGDBw9y9uxZjh8/zvz584F7o5YVKlSgefPm9O7dm4oVKxoVX+RvU4dY9qKCmIiIpElriInI3xUREcGoUaNYunQpsbGxODo60rlzZ+bNm0eJEiWMjicij5mbmxt9+/alb9++9m1Xrlyx39Xy2LFj7Nu3j7179zJlyhQcHR0pXLgwAQEBtGvXjh49emjUUjIdFcSyF33sLyIiadIaYiLyV37++Wfq16+Pt7c3CxYswM3NjYkTJxIXF8e6detUDBMRO19fX8aMGcP333/PzZs3sVgs7N69m1deeYWqVasSERHBunXreP7558mXLx/u7u5UrlyZIUOGsGXLFhUhxHAODg6ARiazC3WIiYhImqxWqwpiIvIAq9XKvHnzmDVrFjdu3ACgevXqzJgxg9atWxucTkSyCrPZTMOGDWnYsKF9259HLQ8cOGAftfz8888xmUz2UctmzZrRp08fKlSoYOAVSE6jDrHsRQUxERFJk9YQE5E/u3nzJiNGjGDNmjUkJCTg4uJC7969mTNnDoULFzY6nohkA6mNWl6+fJmlS5eyfft2jh07xt69e9mzZ4991LJIkSL2UcuePXvi5eVl3AVItubk5ASoQyy70LscERFJk9YQExGA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fp2oqCijo+RYR44cYcSIEezevRur1YqXlxdDhgzhrbfewtXV1eh4IiIiIiJZljrEREQkVcnJyZhMJqNj5DhWq5XPPvuMd955hytXrgD31oybNm2afUxWREREREQejQpiIiKSquTkZMxmLTX5uISHhzNq1CiWLVtGXFwcTk5OdO3alXnz5uHn52d0PBERERGRbEXvdEREJFVWq1UFscdg7969PPHEE+TPn58vv/wSDw8P3nrrLWJjY1mzZo2KYSIiIiIiGUAdYiIikip1iGUcq9XK7NmzmTNnDjdv3gQgICCAmTNn0qJFC4PTiYiIiIhkfyqIiYhIqtQhlv6uX7/Oq6++ypo1a0hMTMTFxYWnnnqKOXPm2O8IKiIiIiIiGU/vdEREJFVWqxUHBwejY2QLW7dupXr16hQtWpTly5dToEAB5s6dS2xsLEuWLFExTERERETkMVOHmIiIpMpqteLk5GR0jCwrMTGRqVOn8vHHHxMWFobJZKJ+/frMmTOHunXrGh1PRERERCRHU0FMRERSpZHJf+fixYu88sorbN26FYvFgpubG4MHD2bmzJl4eXkZHU9ERERERNDIpIiIpEEjk//Mt99+S4UKFShZsiQbN26kWLFizJ8/n6ioKD777DMVw0REREREMhF1iImISKrUIfbXYmNjmTBhAl988QV3797FbDbTokUL5s2bR+XKlY2OJyIiIiIiadA7HRERSZU6xNL222+/0bJlS3Lnzs3cuXOx2WyMGDGCqKgotm/frmKYiIiIiEgmpw4xERFJlQpiD/riiy+YOnUqFy9eBKBcuXJMnjyZnj17GpxMRERERET+CRXEREQkVTabTQUxIDIyklGjRvHNN98QExODo6MjHTp04L333qNUqVJGxxMRERERkX9BI5MiIpKqnN4h9uuvv9KgQQO8vLz4/PPPcXV1ZcKECcTExLBx40YVw0REREREsjB1iImISKpyYoeY1Wrlgw8+4N133+XatWsAVK1alenTp9OuXTuD04mIiIiISHpRQUxERFKVkwpiISEhvPrqq6xatYr4+HicnZ3p0aMH8+bNo0iRIkbHExERERGRdKaRSRERSVVOKIj98MMP1KpVCx8fH5YsWYKXlxczZswgLi6OFStWqBgmIiIiIpJNqUNMRERSZbPZcHTMfi8TFouF6dOn88EHHxASEoLJZKJWrVrMmTOH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CA8Px8/Pj02bNlGpUiWjY2WY0NBQLl26RPPmzVUMExERERERSUX2bI0QEQHi4+Pp0qULTZo04e7du7z11ltcunQpWxfDAN555x0AxowZY3ASERERERGRzEkjkyKSLa1YsYIBAwYQGxtLtWrV2Lp1Kz4+PkbHeiyKFStGeHg4sbGxRkcRERERERHJlNQhJiLZSnh4OPXr16dXr14kJyfz+eefExQUlGOKYdeuXePatWs0a9bM6CgiIiIiIiKZlgpiIpJtzJs3Dx8fH37++WdatGhBaGgogwYNMjrWYzV16lQAxo0bZ3ASERERERGRzEsjkyKS5V2+fJk2bdpw+vRpPD09Wbp0Ke3btzc6liEKFSpEXFwckZGRRkcRERERERHJtNQhJiJZ2pgxYyhZsiSnT5+md+/ehIWF5dhi2Pnz5wkJCaF169ZGRxEREREREcnUHI0OICLybxw+fJiOHTty48YNfHx8WL9+PbVr1zY6lqGmTJkCwJtvvmlwEhERERERkcxNI5MikqVYLBb69+/PkiVLMJvNDB8+nHfffRezWQ2v+fLlw2q1cufOHaOjiIiIiIiIZGrqEBORLGPLli306dOHu3fvUrZsWbZu3UqJEiWMjpUp/Pbbb4SHh9O3b1+jo4iIiIiIiGR6aqkQkUwvNjaWVq1a0b59e2JiYpg1axanT59WMexPJk+eDMAbb7xhcBIREREREZHMTyOTIpKpffnll7z44ovEx8dTp04dNm3aRP78+Y2OlenkyZMHFxcXQkJCjI4iIiIiIiKS6alDTEQypVu3bhEQEMBzzz2HyWTim2++Yf/+/SqGpeLXX38lMjKSrl27Gh1FREREREQkS1BBTEQynbfffpuiRYty5MgROnToQHh4OH369DE6VqY1depUACZMmGBwEhERERERkaxBI5MikmmcPn2a9u3bc+HCBfLmzcu3335L8+bNjY6V6bm7u5MnTx6uX79udBQREREREZEsQR1iImI4q9XKf/7zHypUqMDFixcZOHAgoaGhKob9DTt27CA2NpZevXoZHUVERERERCTLUIeYiBhq7969dO3aldDQUIoXL87mzZupXLmy0bGyjNatW/P9999z69YtChYsaHQcERERERGRLEEdYiJiiMTERLp3707Dhg25c+cOEyZM4MqVKyqG/UO7d+/Gz89PxTAREREREZF/wNHoACKS86xevZp+/foRExNDlSpV2Lx5M8WKFTM6Vpazbt064uPj6du3r9FRREREREREshSNTIrIYxMREUGnTp3Ys2cPzs7OzJs3jxdeeMHoWFlW48aN2b17N3fv3sXT09PoOCIiIiIiIlmGCmIi8lh8+OGHjBw5ksTERJo0acL69etVxHkEVqsVV1dX/Pz8OHv2rNFxREREREREshSNTIpIhrpy5Qrt2rXjxIkTeHh4sGLFCrp06WJ0rCxv6dKlJCUl0b9/f6OjiIiIiIiIZDnqEBORDDNu3DhmzJiB1WrlySefZMmSJTg7OxsdK1uoW7cuBw4cIDo6Gjc3N6PjiIiIiIiIZCkqiIlIugsODqZ9+/Zcu3aNAgUKsG7dOurVq2d0rGwjOTkZFxcXypUrx/Hjx42OIyIiIiIikuWYjQ4gItmH1WplwIABVK9enevXr/Pyyy9z8+ZNFcPS2YIFC0hOTmbIkCFGRxEREREREcmS1CEmIulix44dPPnkk0RERFCqVCm2bNlCmTJljI6VLVWvXp1jx44RFxenEVQREREREZF/QR1iIvJI4uLiaNeuHS1btiQ6Opp33nmHc+fOqRiWQRITEzl27BjVqlVTMUxERERERORf0l0mReRfW7JkCYMHDyYuLo6aNWuyefNmChYsaHSsbO3DDz/EarXyn//8x+goIiIiIiIiWZZGJkXkHwsNDaVdu3YcPHgQV1dXPv30U/r162d0rByhQoUKnDt3joSEBMxmNfmKiIiIiIj8G3o3JSL/yMyZMylcuDAHDx6kTZs2hIWFqRj2mMTGxnL69Glq1qypYpiIiIiIiMgj0MikiPwt58+fp23btpw7d448efKwYsUKWrdubXSsHGXWrFnYbDZGjBhhdBQREREREZEsTSOTIvJQVquVESNG8MEHH2Cz2ejXrx8LFizA0VH19MetdOnS/P7778TFxalDTERERERE5BHoHa2IpGn//v106dKFW7duUbRoUTZu3Ej16tWNjpUjRUREcP78eZo0aaJimIiIiIiIyCPSuyoReYDFYqFnz5488cQT3L59mzFjxnD16lUVwww0ffp0AEaNGmVwEhERERERkaxPI5MiksL69et5+umniYqKokKFCmzZsgU/Pz+jY+V4vr6+3L59m7i4OKOjiIiIiIiIZHnqEBMRACIjI2nWrBldunQhPj6e999/nxMnTqgY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TZo0oXfv3lStWhW418XVoUMHQkJCcHFxsT+vdOnSjBkzhiFDhlC/fn1KlizJ119/neo5/P39qVGjBmvWrLFvGzRoEA4ODnz66af2bXv27KFJkybExMTg6uqKv78/w4cPZ/jw4SmOV758ed55550UXWh/FhoayldffUWDBg0wm82sWrWKmTNnsnbt2hSdb5md1WplyJAhfPHFFwA8//zzfPjhh5jNqgtLxnN3d8fLy4tr164ZHUVERERERCTH0xpi6SwwMJAOHTqwe/dufvnlF7Zs2cLMmTOZP38+/fv35+jRo0RHRz+w/lZcXBznz58HICgoiMGDBz/0PLVq1Urx/dGjRwkODmbJkiX2bTabDavVysWLF6lQoUKaxzp16tRDz5U/f35effVV+/e1a9fm+vXrvPvuu1mmILZz504CAwMJDw+nRIkSbNq06aE/E5H0tGPHDmJjY+3jzSIiIiIiImIsFcQygKurK61ataJVq1a88cYbDBo0iIkTJ9K/f3+io6MpXLgwO3fufOB5Xl5eAOTKlesvz+Hu7p7i++joaIYOHcorr7zywL6+vr7/6joepm7dunz//ffpftz0Fh8fT48ePdi4cSMODg5MmTJFI2vy2E2fPh2A8ePHG5xEREREREREQAWxx6JixYqsXbsWgICAAG7evImjoyP+/v6p7l+1alV27NjBgAED/vY5AgICOHHiBKVLl05zH2dnZ5KTk/9J9DQFBQVRuHDhdDlWRlm2bBkDBw4kNjaWGjVqsHnzZnx8fIyOJTnQ7t278fPzI3/+/EZHEREREREREbSofroKCwujefPmfP311wQHB3Px4kVWrlzJzJkz6dKlCwAtW7akXr16dO3alW3btnHp0iX27dvH+PHjOXjwIAATJ05k6dKlTJw4kZMnT3Ls2DFmzJjx0HO/9tpr7Nu3j5deeomgoCDOnj3LunXr7Ivqw721x3bt2sW1a9cIDQ21by9fvnyK9cj+v0WLFrF06VJOnTrFqVOnmDZtGl988QUvv/zyo/y4Mkx4eDj16tWjT58+JCcnM3/+fA4fPqximBhizZo1JCQk8MwzzxgdRURERERERP5HHWLpyMPDg7p16zJ37lzOnz9PUlISxYsXZ/DgwYwbNw64d0fHzZs3M378eAYMGMDt27fx8fGhcePGFCpUCICmTZuycuVKpkyZwvTp0/H09KRx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", "text/plain": [ "
" ] @@ -393,7 +382,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 11, "metadata": {}, "outputs": [], "source": [ @@ -421,7 +410,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 12, "metadata": {}, "outputs": [ { @@ -446,10 +435,12 @@ "--------------------------------------------------------------------------------\n", "\u001b[33mA1\u001b[0m (to chat_manager):\n", "\n", - "As A1, I have 2 chocolates. \n", + "As the team leader of Team A, it's my responsibility to gather the chocolate count from my team members. I have 1 chocolate. \n", + "\n", + "A0:?, A1:1, A2:?,\n", + "\n", + "B0:?, B1:?, B2:?,\n", "\n", - "A0:?, A1:2, A2:?, \n", - "B0:?, B1:?, B2:?, \n", "C0:?, C1:?, C2:?\n", "\n", "NEXT: A2\n", @@ -457,10 +448,12 @@ "--------------------------------------------------------------------------------\n", "\u001b[33mA2\u001b[0m (to chat_manager):\n", "\n", - "As A2, I have 2 chocolates.\n", + "I have 5 chocolates.\n", + "\n", + "A0:?, A1:1, A2:5,\n", + "\n", + "B0:?, B1:?, B2:?,\n", "\n", - "A0:?, A1:2, A2:2, \n", - "B0:?, B1:?, B2:?, \n", "C0:?, C1:?, C2:?\n", "\n", "NEXT: A0\n", @@ -468,33 +461,42 @@ "--------------------------------------------------------------------------------\n", "\u001b[33mA0\u001b[0m (to chat_manager):\n", "\n", - "As A0, I have 1 chocolate. So, the total chocolate count for team A is 5.\n", + "I have 1 chocolate. Now that I have the counts from A1 and A2, I can calculate our team's total.\n", + "\n", + "A0:1, A1:1, A2:5,\n", + "\n", + "B0:?, B1:?, B2:?,\n", "\n", - "A0:1, A1:2, A2:2, \n", - "B0:?, B1:?, B2:?, \n", "C0:?, C1:?, C2:?\n", "\n", - "Now, I suggest the next team leader to find out their team's tally.\n", + "Team A's total tally is 7 chocolates. It's time to find out the tally from another team. I'll pass this to the next team leader.\n", + "\n", "NEXT: B0\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mB0\u001b[0m (to chat_manager):\n", "\n", - "As B0, I have 3 chocolates. Now, I will ask my team members about their count.\n", + "As the team leader of Team B, it's my responsibility to gather the chocolate count from my team members. I have 1 chocolate.\n", + "\n", + "A0:1, A1:1, A2:5,\n", + "\n", + "B0:1, B1:?, B2:?,\n", "\n", - "B0:3, B1:?, B2:?, \n", - "A0:1, A1:2, A2:2, \n", "C0:?, C1:?, C2:?\n", "\n", + "Now, I need to find out the chocolate counts from B1 and B2.\n", + "\n", "NEXT: B1\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mB1\u001b[0m (to chat_manager):\n", "\n", - "As B1, I have 1 chocolate.\n", + "I have 3 chocolates.\n", + "\n", + "A0:1, A1:1, A2:5,\n", + "\n", + "B0:1, B1:3, B2:?,\n", "\n", - "B0:3, B1:1, B2:?, \n", - "A0:1, A1:2, A2:2, \n", "C0:?, C1:?, C2:?\n", "\n", "NEXT: B2\n", @@ -502,49 +504,77 @@ "--------------------------------------------------------------------------------\n", "\u001b[33mB2\u001b[0m (to chat_manager):\n", "\n", - "As B2, I have 2 chocolates. So, the total chocolate count for team B is 6.\n", + "I have 4 chocolates. Now that I have the counts from B0 and B1, I can calculate our team's total.\n", + "\n", + "A0:1, A1:1, A2:5,\n", + "\n", + "B0:1, B1:3, B2:4,\n", "\n", - "B0:3, B1:1, B2:2, \n", - "A0:1, A1:2, A2:2, \n", "C0:?, C1:?, C2:?\n", "\n", - "Now, I suggest the next team leader to find out their team's tally.\n", + "Team B's total tally is 8 chocolates. It's time to find out the tally from the last team. I'll pass this to the next team leader.\n", + "\n", "NEXT: C0\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mB0\u001b[0m (to chat_manager):\n", "\n", - "As C0, I have 4 chocolates. Now, I will ask my team members about their count.\n", + "I'm sorry for any confusion, but it seems there's been a misunderstanding in the flow of communication. As B0, I should be the one to summarize the tally for Team B and pass the conversation to the next team leader. Let me correct that.\n", "\n", - "C0:4, C1:?, C2:?, \n", - "B0:3, B1:1, B2:2, \n", - "A0:1, A1:2, A2:2, \n", + "A0:1, A1:1, A2:5,\n", "\n", - "NEXT: C1\n", + "B0:1, B1:3, B2:4,\n", + "\n", + "C0:?, C1:?, C2:?\n", + "\n", + "With the counts from B1 and B2, Team B's total tally is indeed 8 chocolates. Now, it's time to find out the tally from Team C. I'll pass this to the next team leader.\n", + "\n", + "NEXT: C0\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mC0\u001b[0m (to chat_manager):\n", "\n", - "As C1, I have 2 chocolates.\n", + "As the team leader of Team C, it's my responsibility to gather the chocolate count from my team members. I have 3 chocolates.\n", "\n", - "C0:4, C1:2, C2:?, \n", - "B0:3, B1:1, B2:2, \n", - "A0:1, A1:2, A2:2, \n", + "A0:1, A1:1, A2:5,\n", + "\n", + "B0:1, B1:3, B2:4,\n", + "\n", + "C0:3, C1:?, C2:?\n", + "\n", + "Now, I need to find out the chocolate counts from C1 and C2.\n", + "\n", + "NEXT: C1\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mC1\u001b[0m (to chat_manager):\n", + "\n", + "I have 3 chocolates.\n", + "\n", + "A0:1, A1:1, A2:5,\n", + "\n", + "B0:1, B1:3, B2:4,\n", + "\n", + "C0:3, C1:3, C2:?\n", "\n", "NEXT: C2\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mC2\u001b[0m (to chat_manager):\n", "\n", - "As C2, I have 1 chocolate. So, the total chocolate count for team C is 7.\n", + "I have 2 chocolates. Now that I have the counts from C0 and C1, I can calculate our team's total.\n", + "\n", + "A0:1, A1:1, A2:5,\n", + "\n", + "B0:1, B1:3, B2:4,\n", + "\n", + "C0:3, C1:3, C2:2\n", "\n", - "C0:4, C1:2, C2:1, \n", - "B0:3, B1:1, B2:2, \n", - "A0:1, A1:2, A2:2, \n", + "Team C's total tally is 8 chocolates. Now that we have the tallies from all teams, we can sum them up.\n", "\n", - "Now, we have the total tally from all nine players. The sum of all three teams' tally is 18. \n", + "Team A's total is 7 chocolates, Team B's total is 8 chocolates, and Team C's total is 8 chocolates. The grand total is 23 chocolates.\n", "\n", - "TERMINATE.\n", + "TERMINATE\n", "\n", "--------------------------------------------------------------------------------\n" ] @@ -552,10 +582,10 @@ { "data": { "text/plain": [ - "ChatResult(chat_history=[{'content': '\\n There are 9 players in this game, split equally into Teams A, B, C. Therefore each team has 3 players, including the team leader.\\n The task is to find out the sum of chocolate count from all nine players. I will now start with my team.\\n NEXT: A1', 'role': 'assistant'}, {'content': 'As A1, I have 2 chocolates. \\n\\nA0:?, A1:2, A2:?, \\nB0:?, B1:?, B2:?, \\nC0:?, C1:?, C2:?\\n\\nNEXT: A2', 'name': 'A1', 'role': 'user'}, {'content': 'As A2, I have 2 chocolates.\\n\\nA0:?, A1:2, A2:2, \\nB0:?, B1:?, B2:?, \\nC0:?, C1:?, C2:?\\n\\nNEXT: A0', 'name': 'A2', 'role': 'user'}, {'content': \"As A0, I have 1 chocolate. So, the total chocolate count for team A is 5.\\n\\nA0:1, A1:2, A2:2, \\nB0:?, B1:?, B2:?, \\nC0:?, C1:?, C2:?\\n\\nNow, I suggest the next team leader to find out their team's tally.\\nNEXT: B0\", 'role': 'assistant'}, {'content': 'As B0, I have 3 chocolates. Now, I will ask my team members about their count.\\n\\nB0:3, B1:?, B2:?, \\nA0:1, A1:2, A2:2, \\nC0:?, C1:?, C2:?\\n\\nNEXT: B1', 'name': 'B0', 'role': 'user'}, {'content': 'As B1, I have 1 chocolate.\\n\\nB0:3, B1:1, B2:?, \\nA0:1, A1:2, A2:2, \\nC0:?, C1:?, C2:?\\n\\nNEXT: B2', 'name': 'B1', 'role': 'user'}, {'content': \"As B2, I have 2 chocolates. So, the total chocolate count for team B is 6.\\n\\nB0:3, B1:1, B2:2, \\nA0:1, A1:2, A2:2, \\nC0:?, C1:?, C2:?\\n\\nNow, I suggest the next team leader to find out their team's tally.\\nNEXT: C0\", 'name': 'B2', 'role': 'user'}, {'content': 'As C0, I have 4 chocolates. Now, I will ask my team members about their count.\\n\\nC0:4, C1:?, C2:?, \\nB0:3, B1:1, B2:2, \\nA0:1, A1:2, A2:2, \\n\\nNEXT: C1', 'name': 'B0', 'role': 'user'}, {'content': 'As C1, I have 2 chocolates.\\n\\nC0:4, C1:2, C2:?, \\nB0:3, B1:1, B2:2, \\nA0:1, A1:2, A2:2, \\n\\nNEXT: C2', 'name': 'C0', 'role': 'user'}, {'content': \"As C2, I have 1 chocolate. So, the total chocolate count for team C is 7.\\n\\nC0:4, C1:2, C2:1, \\nB0:3, B1:1, B2:2, \\nA0:1, A1:2, A2:2, \\n\\nNow, we have the total tally from all nine players. The sum of all three teams' tally is 18. \\n\\nTERMINATE.\", 'name': 'C2', 'role': 'user'}], summary=\"As C2, I have 1 chocolate. So, the total chocolate count for team C is 7.\\n\\nC0:4, C1:2, C2:1, \\nB0:3, B1:1, B2:2, \\nA0:1, A1:2, A2:2, \\n\\nNow, we have the total tally from all nine players. The sum of all three teams' tally is 18. \\n\\n.\", cost=({'total_cost': 0.48207, 'gpt-4-0613': {'cost': 0.48207, 'prompt_tokens': 15865, 'completion_tokens': 102, 'total_tokens': 15967}}, {'total_cost': 0.48207, 'gpt-4-0613': {'cost': 0.48207, 'prompt_tokens': 15865, 'completion_tokens': 102, 'total_tokens': 15967}}), human_input=[])" + "ChatResult(chat_id=None, chat_history=[{'content': '\\n There are 9 players in this game, split equally into Teams A, B, C. Therefore each team has 3 players, including the team leader.\\n The task is to find out the sum of chocolate count from all nine players. I will now start with my team.\\n NEXT: A1', 'role': 'assistant'}, {'content': \"As the team leader of Team A, it's my responsibility to gather the chocolate count from my team members. I have 1 chocolate. \\n\\nA0:?, A1:1, A2:?,\\n\\nB0:?, B1:?, B2:?,\\n\\nC0:?, C1:?, C2:?\\n\\nNEXT: A2\", 'name': 'A1', 'role': 'user'}, {'content': 'I have 5 chocolates.\\n\\nA0:?, A1:1, A2:5,\\n\\nB0:?, B1:?, B2:?,\\n\\nC0:?, C1:?, C2:?\\n\\nNEXT: A0', 'name': 'A2', 'role': 'user'}, {'content': \"I have 1 chocolate. Now that I have the counts from A1 and A2, I can calculate our team's total.\\n\\nA0:1, A1:1, A2:5,\\n\\nB0:?, B1:?, B2:?,\\n\\nC0:?, C1:?, C2:?\\n\\nTeam A's total tally is 7 chocolates. It's time to find out the tally from another team. I'll pass this to the next team leader.\\n\\nNEXT: B0\", 'role': 'assistant'}, {'content': \"As the team leader of Team B, it's my responsibility to gather the chocolate count from my team members. I have 1 chocolate.\\n\\nA0:1, A1:1, A2:5,\\n\\nB0:1, B1:?, B2:?,\\n\\nC0:?, C1:?, C2:?\\n\\nNow, I need to find out the chocolate counts from B1 and B2.\\n\\nNEXT: B1\", 'name': 'B0', 'role': 'user'}, {'content': 'I have 3 chocolates.\\n\\nA0:1, A1:1, A2:5,\\n\\nB0:1, B1:3, B2:?,\\n\\nC0:?, C1:?, C2:?\\n\\nNEXT: B2', 'name': 'B1', 'role': 'user'}, {'content': \"I have 4 chocolates. Now that I have the counts from B0 and B1, I can calculate our team's total.\\n\\nA0:1, A1:1, A2:5,\\n\\nB0:1, B1:3, B2:4,\\n\\nC0:?, C1:?, C2:?\\n\\nTeam B's total tally is 8 chocolates. It's time to find out the tally from the last team. I'll pass this to the next team leader.\\n\\nNEXT: C0\", 'name': 'B2', 'role': 'user'}, {'content': \"I'm sorry for any confusion, but it seems there's been a misunderstanding in the flow of communication. As B0, I should be the one to summarize the tally for Team B and pass the conversation to the next team leader. Let me correct that.\\n\\nA0:1, A1:1, A2:5,\\n\\nB0:1, B1:3, B2:4,\\n\\nC0:?, C1:?, C2:?\\n\\nWith the counts from B1 and B2, Team B's total tally is indeed 8 chocolates. Now, it's time to find out the tally from Team C. I'll pass this to the next team leader.\\n\\nNEXT: C0\", 'name': 'B0', 'role': 'user'}, {'content': \"As the team leader of Team C, it's my responsibility to gather the chocolate count from my team members. I have 3 chocolates.\\n\\nA0:1, A1:1, A2:5,\\n\\nB0:1, B1:3, B2:4,\\n\\nC0:3, C1:?, C2:?\\n\\nNow, I need to find out the chocolate counts from C1 and C2.\\n\\nNEXT: C1\", 'name': 'C0', 'role': 'user'}, {'content': 'I have 3 chocolates.\\n\\nA0:1, A1:1, A2:5,\\n\\nB0:1, B1:3, B2:4,\\n\\nC0:3, C1:3, C2:?\\n\\nNEXT: C2', 'name': 'C1', 'role': 'user'}, {'content': \"I have 2 chocolates. Now that I have the counts from C0 and C1, I can calculate our team's total.\\n\\nA0:1, A1:1, A2:5,\\n\\nB0:1, B1:3, B2:4,\\n\\nC0:3, C1:3, C2:2\\n\\nTeam C's total tally is 8 chocolates. Now that we have the tallies from all teams, we can sum them up.\\n\\nTeam A's total is 7 chocolates, Team B's total is 8 chocolates, and Team C's total is 8 chocolates. The grand total is 23 chocolates.\\n\\nTERMINATE\", 'name': 'C2', 'role': 'user'}], summary=\"I have 2 chocolates. Now that I have the counts from C0 and C1, I can calculate our team's total.\\n\\nA0:1, A1:1, A2:5,\\n\\nB0:1, B1:3, B2:4,\\n\\nC0:3, C1:3, C2:2\\n\\nTeam C's total tally is 8 chocolates. Now that we have the tallies from all teams, we can sum them up.\\n\\nTeam A's total is 7 chocolates, Team B's total is 8 chocolates, and Team C's total is 8 chocolates. The grand total is 23 chocolates.\\n\\n\", cost={'usage_including_cached_inference': {'total_cost': 0.5525399999999999, 'gpt-4': {'cost': 0.5525399999999999, 'prompt_tokens': 18174, 'completion_tokens': 122, 'total_tokens': 18296}}, 'usage_excluding_cached_inference': {'total_cost': 0.5525399999999999, 'gpt-4': {'cost': 0.5525399999999999, 'prompt_tokens': 18174, 'completion_tokens': 122, 'total_tokens': 18296}}}, human_input=[])" ] }, - "execution_count": 11, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } @@ -612,7 +642,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.12" + "version": "3.10.13" } }, "nbformat": 4, diff --git a/notebook/agentchat_groupchat_stateflow.ipynb b/notebook/agentchat_groupchat_stateflow.ipynb index 461687b9070..b8810d2fb63 100644 --- a/notebook/agentchat_groupchat_stateflow.ipynb +++ b/notebook/agentchat_groupchat_stateflow.ipynb @@ -43,7 +43,7 @@ "config_list = autogen.config_list_from_json(\n", " \"OAI_CONFIG_LIST\",\n", " filter_dict={\n", - " \"model\": [\"gpt-4\", \"gpt-4-1106-preview\"],\n", + " \"tags\": [\"gpt-4\", \"gpt-4-32k\"],\n", " },\n", ")" ] @@ -87,19 +87,20 @@ "cell_type": "code", "execution_count": 2, "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/home/ykw5399/maintain/autogen/autogen/agentchat/user_proxy_agent.py:83: UserWarning: Using None to signal a default code_execution_config is deprecated. Use {} to use default or False to disable code execution.\n", - " super().__init__(\n" - ] - } - ], + "outputs": [], "source": [ + "import tempfile\n", + "\n", + "from autogen.coding import LocalCommandLineCodeExecutor\n", + "\n", + "temp_dir = tempfile.TemporaryDirectory()\n", + "executor = LocalCommandLineCodeExecutor(\n", + " timeout=10, # Timeout for each code execution in seconds.\n", + " work_dir=temp_dir.name, # Use the temporary directory to store the code files.\n", + ")\n", + "\n", "gpt4_config = {\n", - " \"cache_seed\": 42, # change the cache_seed for different trials\n", + " \"cache_seed\": False, # change the cache_seed for different trials\n", " \"temperature\": 0,\n", " \"config_list\": config_list,\n", " \"timeout\": 120,\n", @@ -107,8 +108,10 @@ "\n", "initializer = autogen.UserProxyAgent(\n", " name=\"Init\",\n", + " code_execution_config=False,\n", ")\n", "\n", + "\n", "coder = autogen.AssistantAgent(\n", " name=\"Retrieve_Action_1\",\n", " llm_config=gpt4_config,\n", @@ -122,11 +125,7 @@ " name=\"Retrieve_Action_2\",\n", " system_message=\"Executor. Execute the code written by the Coder and report the result.\",\n", " human_input_mode=\"NEVER\",\n", - " code_execution_config={\n", - " \"last_n_messages\": 3,\n", - " \"work_dir\": \"paper\",\n", - " \"use_docker\": False,\n", - " }, # Please set use_docker=True if docker is available to run the generated code. Using docker is safer than running the generated code directly.\n", + " code_execution_config={\"executor\": executor},\n", ")\n", "scientist = autogen.AssistantAgent(\n", " name=\"Research_Action_1\",\n", @@ -189,19 +188,20 @@ "import feedparser\n", "\n", "# Define the base URL for the arXiv API\n", - "ARXIV_API_URL = 'http://export.arxiv.org/api/query?'\n", + "ARXIV_API_URL = \"http://export.arxiv.org/api/query?\"\n", "\n", "# Define the search parameters\n", - "search_query = 'all:\"LLM applications\"'\n", - "start_date = (datetime.now() - timedelta(days=7)).strftime('%Y%m%d%H%M%S')\n", - "end_date = datetime.now().strftime('%Y%m%d%H%M%S')\n", + "search_query = \"all:\\\"LLM applications\\\"\"\n", "start = 0\n", "max_results = 10\n", - "sort_by = 'submittedDate'\n", - "sort_order = 'descending'\n", + "sort_by = \"submittedDate\"\n", + "sort_order = \"descending\"\n", + "\n", + "# Calculate the date one week ago from today\n", + "one_week_ago = (datetime.now() - timedelta(days=7)).strftime('%Y-%m-%dT%H:%M:%SZ')\n", "\n", "# Construct the query\n", - "query = f'search_query={search_query}&sortBy={sort_by}&sortOrder={sort_order}&start={start}&max_results={max_results}'\n", + "query = f\"search_query={search_query}&start={start}&max_results={max_results}&sortBy={sort_by}&sortOrder={sort_order}&submittedDateRange={one_week_ago}-\"\n", "\n", "# Send the request to the arXiv API\n", "response = requests.get(ARXIV_API_URL + query)\n", @@ -215,299 +215,298 @@ " print(\"Authors:\", ', '.join(author.name for author in entry.authors))\n", " print(\"Abstract:\", entry.summary)\n", " print(\"Link:\", entry.link)\n", - " print(\"\\n\")\n", - "\n", - "# Check if we have at least 5 papers, if not, adjust the search or notify\n", - "if len(feed.entries) < 5:\n", - " print(\"Less than 5 papers found. Consider adjusting the search parameters or timeframe.\")\n", + " print(\"\\n---\\n\")\n", "```\n", "\n", - "This script will print the title, authors, abstract, and link for each paper related to \"LLM applications\" from the last week, up to a maximum of 10 papers. If fewer than 5 papers are found, it will notify the user to consider adjusting the search parameters or timeframe.\n", + "This script will print the title, authors, abstract, and link for each paper related to \"LLM applications\" that was submitted in the last week, up to a maximum of 10 papers. If you want to ensure that the papers are from different domains, you might need to manually check the categories of the papers or refine the search query to target specific domains.\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[31m\n", - ">>>>>>>> EXECUTING CODE BLOCK 0 (inferred language is python)...\u001b[0m\n", + ">>>>>>>> EXECUTING CODE BLOCK (inferred language is python)...\u001b[0m\n", "\u001b[33mRetrieve_Action_2\u001b[0m (to chat_manager):\n", "\n", "exitcode: 0 (execution succeeded)\n", - "Code output: \n", - "Title: PRSA: Prompt Reverse Stealing Attacks against Large Language Models\n", - "Authors: Yong Yang, Xuhong Zhang, Yi Jiang, Xi Chen, Haoyu Wang, Shouling Ji, Zonghui Wang\n", - "Abstract: Prompt, recognized as crucial intellectual property, enables large language\n", - "models (LLMs) to perform specific tasks without the need of fine-tuning,\n", - "underscoring their escalating importance. With the rise of prompt-based\n", - "services, such as prompt marketplaces and LLM applications, providers often\n", - "display prompts' capabilities through input-output examples to attract users.\n", - "However, this paradigm raises a pivotal security concern: does the exposure of\n", - "input-output pairs pose the risk of potential prompt leakage, infringing on the\n", - "intellectual property rights of the developers? To our knowledge, this problem\n", - "still has not been comprehensively explored yet. To remedy this gap, in this\n", - "paper, we perform the first in depth exploration and propose a novel attack\n", - "framework for reverse-stealing prompts against commercial LLMs, namely PRSA.\n", - "The main idea of PRSA is that by analyzing the critical features of the\n", - "input-output pairs, we mimic and gradually infer (steal) the target prompts. In\n", - "detail, PRSA mainly consists of two key phases: prompt mutation and prompt\n", - "pruning. In the mutation phase, we propose a prompt attention algorithm based\n", - "on differential feedback to capture these critical features for effectively\n", - "inferring the target prompts. In the prompt pruning phase, we identify and mask\n", - "the words dependent on specific inputs, enabling the prompts to accommodate\n", - "diverse inputs for generalization. Through extensive evaluation, we verify that\n", - "PRSA poses a severe threat in real world scenarios. We have reported these\n", - "findings to prompt service providers and actively collaborate with them to take\n", - "protective measures for prompt copyright.\n", - "Link: http://arxiv.org/abs/2402.19200v1\n", - "\n", - "\n", - "Title: Political Compass or Spinning Arrow? Towards More Meaningful Evaluations\n", - " for Values and Opinions in Large Language Models\n", - "Authors: Paul RΓΆttger, Valentin Hofmann, Valentina Pyatkin, Musashi Hinck, Hannah Rose Kirk, Hinrich SchΓΌtze, Dirk Hovy\n", - "Abstract: Much recent work seeks to evaluate values and opinions in large language\n", - "models (LLMs) using multiple-choice surveys and questionnaires. Most of this\n", - "work is motivated by concerns around real-world LLM applications. For example,\n", - "politically-biased LLMs may subtly influence society when they are used by\n", - "millions of people. Such real-world concerns, however, stand in stark contrast\n", - "to the artificiality of current evaluations: real users do not typically ask\n", - "LLMs survey questions. Motivated by this discrepancy, we challenge the\n", - "prevailing constrained evaluation paradigm for values and opinions in LLMs and\n", - "explore more realistic unconstrained evaluations. As a case study, we focus on\n", - "the popular Political Compass Test (PCT). In a systematic review, we find that\n", - "most prior work using the PCT forces models to comply with the PCT's\n", - "multiple-choice format. We show that models give substantively different\n", - "answers when not forced; that answers change depending on how models are\n", - "forced; and that answers lack paraphrase robustness. Then, we demonstrate that\n", - "models give different answers yet again in a more realistic open-ended answer\n", - "setting. We distill these findings into recommendations and open challenges in\n", - "evaluating values and opinions in LLMs.\n", - "Link: http://arxiv.org/abs/2402.16786v1\n", - "\n", - "\n", - "Title: Large Language Models as Urban Residents: An LLM Agent Framework for\n", - " Personal Mobility Generation\n", - "Authors: Jiawei Wang, Renhe Jiang, Chuang Yang, Zengqing Wu, Makoto Onizuka, Ryosuke Shibasaki, Chuan Xiao\n", - "Abstract: This paper introduces a novel approach using Large Language Models (LLMs)\n", - "integrated into an agent framework for flexible and efficient personal mobility\n", - "generation. LLMs overcome the limitations of previous models by efficiently\n", - "processing semantic data and offering versatility in modeling various tasks.\n", - "Our approach addresses the critical need to align LLMs with real-world urban\n", - "mobility data, focusing on three research questions: aligning LLMs with rich\n", - "activity data, developing reliable activity generation strategies, and\n", - "exploring LLM applications in urban mobility. The key technical contribution is\n", - "a novel LLM agent framework that accounts for individual activity patterns and\n", - "motivations, including a self-consistency approach to align LLMs with\n", - "real-world activity data and a retrieval-augmented strategy for interpretable\n", - "activity generation. In experimental studies, comprehensive validation is\n", - "performed using real-world data. This research marks the pioneering work of\n", - "designing an LLM agent framework for activity generation based on real-world\n", - "human activity data, offering a promising tool for urban mobility analysis.\n", - "Link: http://arxiv.org/abs/2402.14744v1\n", - "\n", - "\n", - "Title: An Evaluation of Large Language Models in Bioinformatics Research\n", - "Authors: Hengchuang Yin, Zhonghui Gu, Fanhao Wang, Yiparemu Abuduhaibaier, Yanqiao Zhu, Xinming Tu, Xian-Sheng Hua, Xiao Luo, Yizhou Sun\n", - "Abstract: Large language models (LLMs) such as ChatGPT have gained considerable\n", - "interest across diverse research communities. Their notable ability for text\n", - "completion and generation has inaugurated a novel paradigm for\n", - "language-interfaced problem solving. However, the potential and efficacy of\n", - "these models in bioinformatics remain incompletely explored. In this work, we\n", - "study the performance LLMs on a wide spectrum of crucial bioinformatics tasks.\n", - "These tasks include the identification of potential coding regions, extraction\n", - "of named entities for genes and proteins, detection of antimicrobial and\n", - "anti-cancer peptides, molecular optimization, and resolution of educational\n", - "bioinformatics problems. Our findings indicate that, given appropriate prompts,\n", - "LLMs like GPT variants can successfully handle most of these tasks. In\n", - "addition, we provide a thorough analysis of their limitations in the context of\n", - "complicated bioinformatics tasks. In conclusion, we believe that this work can\n", - "provide new perspectives and motivate future research in the field of LLMs\n", - "applications, AI for Science and bioinformatics.\n", - "Link: http://arxiv.org/abs/2402.13714v1\n", - "\n", - "\n", - "Title: Privacy-Preserving Instructions for Aligning Large Language Models\n", - "Authors: Da Yu, Peter Kairouz, Sewoong Oh, Zheng Xu\n", - "Abstract: Service providers of large language model (LLM) applications collect user\n", - "instructions in the wild and use them in further aligning LLMs with users'\n", - "intentions. These instructions, which potentially contain sensitive\n", - "information, are annotated by human workers in the process. This poses a new\n", - "privacy risk not addressed by the typical private optimization. To this end, we\n", - "propose using synthetic instructions to replace real instructions in data\n", - "annotation and model fine-tuning. Formal differential privacy is guaranteed by\n", - "generating those synthetic instructions using privately fine-tuned generators.\n", - "Crucial in achieving the desired utility is our novel filtering algorithm that\n", - "matches the distribution of the synthetic instructions to that of the real\n", - "ones. In both supervised fine-tuning and reinforcement learning from human\n", - "feedback, our extensive experiments demonstrate the high utility of the final\n", - "set of synthetic instructions by showing comparable results to real\n", - "instructions. In supervised fine-tuning, models trained with private synthetic\n", - "instructions outperform leading open-source models such as Vicuna.\n", - "Link: http://arxiv.org/abs/2402.13659v1\n", - "\n", - "\n", - "Title: Ain't Misbehavin' -- Using LLMs to Generate Expressive Robot Behavior in\n", - " Conversations with the Tabletop Robot Haru\n", - "Authors: Zining Wang, Paul Reisert, Eric Nichols, Randy Gomez\n", - "Abstract: Social robots aim to establish long-term bonds with humans through engaging\n", - "conversation. However, traditional conversational approaches, reliant on\n", - "scripted interactions, often fall short in maintaining engaging conversations.\n", - "This paper addresses this limitation by integrating large language models\n", - "(LLMs) into social robots to achieve more dynamic and expressive conversations.\n", - "We introduce a fully-automated conversation system that leverages LLMs to\n", - "generate robot responses with expressive behaviors, congruent with the robot's\n", - "personality. We incorporate robot behavior with two modalities: 1) a\n", - "text-to-speech (TTS) engine capable of various delivery styles, and 2) a\n", - "library of physical actions for the robot. We develop a custom,\n", - "state-of-the-art emotion recognition model to dynamically select the robot's\n", - "tone of voice and utilize emojis from LLM output as cues for generating robot\n", - "actions. A demo of our system is available here. To illuminate design and\n", - "implementation issues, we conduct a pilot study where volunteers chat with a\n", - "social robot using our proposed system, and we analyze their feedback,\n", - "conducting a rigorous error analysis of chat transcripts. Feedback was\n", - "overwhelmingly positive, with participants commenting on the robot's empathy,\n", - "helpfulness, naturalness, and entertainment. Most negative feedback was due to\n", - "automatic speech recognition (ASR) errors which had limited impact on\n", - "conversations. However, we observed a small class of errors, such as the LLM\n", - "repeating itself or hallucinating fictitious information and human responses,\n", - "that have the potential to derail conversations, raising important issues for\n", - "LLM application.\n", - "Link: http://arxiv.org/abs/2402.11571v1\n", - "\n", - "\n", - "Title: Fine-tuning Large Language Model (LLM) Artificial Intelligence Chatbots\n", - " in Ophthalmology and LLM-based evaluation using GPT-4\n", - "Authors: Ting Fang Tan, Kabilan Elangovan, Liyuan Jin, Yao Jie, Li Yong, Joshua Lim, Stanley Poh, Wei Yan Ng, Daniel Lim, Yuhe Ke, Nan Liu, Daniel Shu Wei Ting\n", - "Abstract: Purpose: To assess the alignment of GPT-4-based evaluation to human clinician\n", - "experts, for the evaluation of responses to ophthalmology-related patient\n", - "queries generated by fine-tuned LLM chatbots. Methods: 400 ophthalmology\n", - "questions and paired answers were created by ophthalmologists to represent\n", - "commonly asked patient questions, divided into fine-tuning (368; 92%), and\n", - "testing (40; 8%). We find-tuned 5 different LLMs, including LLAMA2-7b,\n", - "LLAMA2-7b-Chat, LLAMA2-13b, and LLAMA2-13b-Chat. For the testing dataset,\n", - "additional 8 glaucoma QnA pairs were included. 200 responses to the testing\n", - "dataset were generated by 5 fine-tuned LLMs for evaluation. A customized\n", - "clinical evaluation rubric was used to guide GPT-4 evaluation, grounded on\n", - "clinical accuracy, relevance, patient safety, and ease of understanding. GPT-4\n", - "evaluation was then compared against ranking by 5 clinicians for clinical\n", - "alignment. Results: Among all fine-tuned LLMs, GPT-3.5 scored the highest\n", - "(87.1%), followed by LLAMA2-13b (80.9%), LLAMA2-13b-chat (75.5%),\n", - "LLAMA2-7b-Chat (70%) and LLAMA2-7b (68.8%) based on the GPT-4 evaluation. GPT-4\n", - "evaluation demonstrated significant agreement with human clinician rankings,\n", - "with Spearman and Kendall Tau correlation coefficients of 0.90 and 0.80\n", - "respectively; while correlation based on Cohen Kappa was more modest at 0.50.\n", - "Notably, qualitative analysis and the glaucoma sub-analysis revealed clinical\n", - "inaccuracies in the LLM-generated responses, which were appropriately\n", - "identified by the GPT-4 evaluation. Conclusion: The notable clinical alignment\n", - "of GPT-4 evaluation highlighted its potential to streamline the clinical\n", - "evaluation of LLM chatbot responses to healthcare-related queries. By\n", - "complementing the existing clinician-dependent manual grading, this efficient\n", - "and automated evaluation could assist the validation of future developments in\n", - "LLM applications for healthcare.\n", - "Link: http://arxiv.org/abs/2402.10083v1\n", - "\n", - "\n", - "Title: Unmemorization in Large Language Models via Self-Distillation and\n", - " Deliberate Imagination\n", - "Authors: Yijiang River Dong, Hongzhou Lin, Mikhail Belkin, Ramon Huerta, Ivan VuliΔ‡\n", - "Abstract: While displaying impressive generation capabilities across many tasks, Large\n", - "Language Models (LLMs) still struggle with crucial issues of privacy violation\n", - "and unwanted exposure of sensitive data. This raises an essential question: how\n", - "should we prevent such undesired behavior of LLMs while maintaining their\n", - "strong generation and natural language understanding (NLU) capabilities? In\n", - "this work, we introduce a novel approach termed deliberate imagination in the\n", - "context of LLM unlearning. Instead of trying to forget memorized data, we\n", - "employ a self-distillation framework, guiding LLMs to deliberately imagine\n", - "alternative scenarios. As demonstrated in a wide range of experiments, the\n", - "proposed method not only effectively unlearns targeted text but also preserves\n", - "the LLMs' capabilities in open-ended generation tasks as well as in NLU tasks.\n", - "Our results demonstrate the usefulness of this approach across different models\n", - "and sizes, and also with parameter-efficient fine-tuning, offering a novel\n", - "pathway to addressing the challenges with private and sensitive data in LLM\n", - "applications.\n", - "Link: http://arxiv.org/abs/2402.10052v1\n", - "\n", - "\n", - "Title: Anchor-based Large Language Models\n", - "Authors: Jianhui Pang, Fanghua Ye, Derek F. Wong, Longyue Wang\n", - "Abstract: Large language models (LLMs) predominantly employ decoder-only transformer\n", - "architectures, necessitating the retention of keys/values information for\n", - "historical tokens to provide contextual information and avoid redundant\n", - "computation. However, the substantial size and parameter volume of these LLMs\n", - "require massive GPU memory. This memory demand increases with the length of the\n", - "input text, leading to an urgent need for more efficient methods of information\n", - "storage and processing. This study introduces Anchor-based LLMs (AnLLMs), which\n", - "utilize an innovative anchor-based self-attention network (AnSAN) and also an\n", - "anchor-based inference strategy. This approach enables LLMs to compress\n", - "sequence information into an anchor token, reducing the keys/values cache and\n", - "enhancing inference efficiency. Experiments on question-answering benchmarks\n", - "reveal that AnLLMs maintain similar accuracy levels while achieving up to 99%\n", - "keys/values cache reduction and up to 3.5 times faster inference. Despite a\n", - "minor compromise in accuracy, the substantial enhancements of AnLLMs employing\n", - "the AnSAN technique in resource utilization and computational efficiency\n", - "underscore their potential for practical LLM applications.\n", - "Link: http://arxiv.org/abs/2402.07616v2\n", - "\n", - "\n", - "Title: T-RAG: Lessons from the LLM Trenches\n", - "Authors: Masoomali Fatehkia, Ji Kim Lucas, Sanjay Chawla\n", - "Abstract: Large Language Models (LLM) have shown remarkable language capabilities\n", - "fueling attempts to integrate them into applications across a wide range of\n", - "domains. An important application area is question answering over private\n", - "enterprise documents where the main considerations are data security, which\n", - "necessitates applications that can be deployed on-prem, limited computational\n", - "resources and the need for a robust application that correctly responds to\n", - "queries. Retrieval-Augmented Generation (RAG) has emerged as the most prominent\n", - "framework for building LLM-based applications. While building a RAG is\n", - "relatively straightforward, making it robust and a reliable application\n", - "requires extensive customization and relatively deep knowledge of the\n", - "application domain. We share our experiences building and deploying an LLM\n", - "application for question answering over private organizational documents. Our\n", - "application combines the use of RAG with a finetuned open-source LLM.\n", - "Additionally, our system, which we call Tree-RAG (T-RAG), uses a tree structure\n", - "to represent entity hierarchies within the organization. This is used to\n", - "generate a textual description to augment the context when responding to user\n", - "queries pertaining to entities within the organization's hierarchy. Our\n", - "evaluations show that this combination performs better than a simple RAG or\n", - "finetuning implementation. Finally, we share some lessons learned based on our\n", - "experiences building an LLM application for real-world use.\n", - "Link: http://arxiv.org/abs/2402.07483v1\n", - "\n", + "Code output: Title: Adapting LLMs for Efficient Context Processing through Soft Prompt\n", + " Compression\n", + "Authors: Cangqing Wang, Yutian Yang, Ruisi Li, Dan Sun, Ruicong Cai, Yuzhu Zhang, Chengqian Fu, Lillian Floyd\n", + "Abstract: The rapid advancement of Large Language Models (LLMs) has inaugurated a\n", + "transformative epoch in natural language processing, fostering unprecedented\n", + "proficiency in text generation, comprehension, and contextual scrutiny.\n", + "Nevertheless, effectively handling extensive contexts, crucial for myriad\n", + "applications, poses a formidable obstacle owing to the intrinsic constraints of\n", + "the models' context window sizes and the computational burdens entailed by\n", + "their operations. This investigation presents an innovative framework that\n", + "strategically tailors LLMs for streamlined context processing by harnessing the\n", + "synergies among natural language summarization, soft prompt compression, and\n", + "augmented utility preservation mechanisms. Our methodology, dubbed\n", + "SoftPromptComp, amalgamates natural language prompts extracted from\n", + "summarization methodologies with dynamically generated soft prompts to forge a\n", + "concise yet semantically robust depiction of protracted contexts. This\n", + "depiction undergoes further refinement via a weighting mechanism optimizing\n", + "information retention and utility for subsequent tasks. We substantiate that\n", + "our framework markedly diminishes computational overhead and enhances LLMs'\n", + "efficacy across various benchmarks, while upholding or even augmenting the\n", + "caliber of the produced content. By amalgamating soft prompt compression with\n", + "sophisticated summarization, SoftPromptComp confronts the dual challenges of\n", + "managing lengthy contexts and ensuring model scalability. Our findings point\n", + "towards a propitious trajectory for augmenting LLMs' applicability and\n", + "efficiency, rendering them more versatile and pragmatic for real-world\n", + "applications. This research enriches the ongoing discourse on optimizing\n", + "language models, providing insights into the potency of soft prompts and\n", + "summarization techniques as pivotal instruments for the forthcoming generation\n", + "of NLP solutions.\n", + "Link: http://arxiv.org/abs/2404.04997v1\n", + "\n", + "---\n", + "\n", + "Title: Explainable Traffic Flow Prediction with Large Language Models\n", + "Authors: Xusen Guo, Qiming Zhang, Mingxing Peng, Meixin Zhu, Hao, Yang\n", + "Abstract: Traffic flow prediction is crucial for urban planning, transportation\n", + "management, and infrastructure development. However, achieving both accuracy\n", + "and interpretability in prediction models remains challenging due to the\n", + "complexity of traffic data and the inherent opacity of deep learning\n", + "methodologies. In this paper, we propose a novel approach, Traffic Flow\n", + "Prediction LLM (TF-LLM), which leverages large language models (LLMs) to\n", + "generate interpretable traffic flow predictions. By transferring multi-modal\n", + "traffic data into natural language descriptions, TF-LLM captures complex\n", + "spatial-temporal patterns and external factors such as weather conditions,\n", + "Points of Interest (PoIs), date, and holidays. We fine-tune the LLM framework\n", + "using language-based instructions to align with spatial-temporal traffic flow\n", + "data. Our comprehensive multi-modal traffic flow dataset (CATraffic) in\n", + "California enables the evaluation of TF-LLM against state-of-the-art deep\n", + "learning baselines. Results demonstrate TF-LLM's competitive accuracy while\n", + "providing intuitive and interpretable predictions. We discuss the\n", + "spatial-temporal and input dependencies for explainable future flow\n", + "forecasting, showcasing TF-LLM's potential for diverse city prediction tasks.\n", + "This paper contributes to advancing explainable traffic prediction models and\n", + "lays a foundation for future exploration of LLM applications in transportation.\n", + "Link: http://arxiv.org/abs/2404.02937v2\n", + "\n", + "---\n", + "\n", + "Title: Designing Child-Centric AI Learning Environments: Insights from\n", + " LLM-Enhanced Creative Project-Based Learning\n", + "Authors: Siyu Zha, Yuehan Qiao, Qingyu Hu, Zhongsheng Li, Jiangtao Gong, Yingqing Xu\n", + "Abstract: Project-based learning (PBL) is an instructional method that is very helpful\n", + "in nurturing students' creativity, but it requires significant time and energy\n", + "from both students and teachers. Large language models (LLMs) have been proven\n", + "to assist in creative tasks, yet much controversy exists regarding their role\n", + "in fostering creativity. This paper explores the potential of LLMs in PBL\n", + "settings, with a special focus on fostering creativity. We began with an\n", + "exploratory study involving 12 middle school students and identified five\n", + "design considerations for LLM applications in PBL. Building on this, we\n", + "developed an LLM-empowered, 48-hour PBL program and conducted an instructional\n", + "experiment with 31 middle school students. Our results indicated that LLMs can\n", + "enhance every stage of PBL. Additionally, we also discovered ambivalent\n", + "perspectives among students and mentors toward LLM usage. Furthermore, we\n", + "explored the challenge and design implications of integrating LLMs into PBL and\n", + "reflected on the program. By bridging AI advancements into educational\n", + "practice, our work aims to inspire further discourse and investigation into\n", + "harnessing AI's potential in child-centric educational settings.\n", + "Link: http://arxiv.org/abs/2403.16159v2\n", + "\n", + "---\n", + "\n", + "Title: The opportunities and risks of large language models in mental health\n", + "Authors: Hannah R. Lawrence, Renee A. Schneider, Susan B. Rubin, Maja J. Mataric, Daniel J. McDuff, Megan Jones Bell\n", + "Abstract: Global rates of mental health concerns are rising and there is increasing\n", + "realization that existing models of mental healthcare will not adequately\n", + "expand to meet the demand. With the emergence of large language models (LLMs)\n", + "has come great optimism regarding their promise to create novel, large-scale\n", + "solutions to support mental health. Despite their nascence, LLMs have already\n", + "been applied to mental health-related tasks. In this review, we summarize the\n", + "extant literature on efforts to use LLMs to provide mental health education,\n", + "assessment, and intervention and highlight key opportunities for positive\n", + "impact in each area. We then highlight risks associated with LLMs application\n", + "to mental health and encourage adoption of strategies to mitigate these risks.\n", + "The urgent need for mental health support must be balanced with responsible\n", + "development, testing, and deployment of mental health LLMs. Especially critical\n", + "is ensuring that mental health LLMs are fine-tuned for mental health, enhance\n", + "mental health equity, adhere to ethical standards, and that people, including\n", + "those with lived experience with mental health concerns, are involved in all\n", + "stages from development through deployment. Prioritizing these efforts will\n", + "minimize potential harms to mental health and maximize the likelihood that LLMs\n", + "will positively impact mental health globally.\n", + "Link: http://arxiv.org/abs/2403.14814v2\n", + "\n", + "---\n", + "\n", + "Title: Large Language Models for Blockchain Security: A Systematic Literature\n", + " Review\n", + "Authors: Zheyuan He, Zihao Li, Sen Yang\n", + "Abstract: Large Language Models (LLMs) have emerged as powerful tools in various\n", + "domains involving blockchain security (BS). Several recent studies are\n", + "exploring LLMs applied to BS. However, there remains a gap in our understanding\n", + "regarding the full scope of applications, impacts, and potential constraints of\n", + "LLMs on blockchain security. To fill this gap, we conduct a literature review\n", + "on LLM4BS.\n", + " As the first review of LLM's application on blockchain security, our study\n", + "aims to comprehensively analyze existing research and elucidate how LLMs\n", + "contribute to enhancing the security of blockchain systems. Through a thorough\n", + "examination of scholarly works, we delve into the integration of LLMs into\n", + "various aspects of blockchain security. We explore the mechanisms through which\n", + "LLMs can bolster blockchain security, including their applications in smart\n", + "contract auditing, identity verification, anomaly detection, vulnerable repair,\n", + "and so on. Furthermore, we critically assess the challenges and limitations\n", + "associated with leveraging LLMs for blockchain security, considering factors\n", + "such as scalability, privacy concerns, and adversarial attacks. Our review\n", + "sheds light on the opportunities and potential risks inherent in this\n", + "convergence, providing valuable insights for researchers, practitioners, and\n", + "policymakers alike.\n", + "Link: http://arxiv.org/abs/2403.14280v2\n", + "\n", + "---\n", + "\n", + "Title: Do Large Language Model Understand Multi-Intent Spoken Language ?\n", + "Authors: Shangjian Yin, Peijie Huang, Yuhong Xu, Haojing Huang, Jiatian Chen\n", + "Abstract: This study marks a significant advancement by harnessing Large Language\n", + "Models (LLMs) for multi-intent spoken language understanding (SLU), proposing a\n", + "unique methodology that capitalizes on the generative power of LLMs within an\n", + "SLU context. Our innovative technique reconfigures entity slots specifically\n", + "for LLM application in multi-intent SLU environments and introduces the concept\n", + "of Sub-Intent Instruction (SII), enhancing the dissection and interpretation of\n", + "intricate, multi-intent communication within varied domains. The resultant\n", + "datasets, dubbed LM-MixATIS and LM-MixSNIPS, are crafted from pre-existing\n", + "benchmarks. Our research illustrates that LLMs can match and potentially excel\n", + "beyond the capabilities of current state-of-the-art multi-intent SLU models. It\n", + "further explores LLM efficacy across various intent configurations and dataset\n", + "proportions. Moreover, we introduce two pioneering metrics, Entity Slot\n", + "Accuracy (ESA) and Combined Semantic Accuracy (CSA), to provide an in-depth\n", + "analysis of LLM proficiency in this complex field.\n", + "Link: http://arxiv.org/abs/2403.04481v2\n", + "\n", + "---\n", + "\n", + "Title: Breaking the Language Barrier: Can Direct Inference Outperform\n", + " Pre-Translation in Multilingual LLM Applications?\n", + "Authors: Yotam Intrator, Matan Halfon, Roman Goldenberg, Reut Tsarfaty, Matan Eyal, Ehud Rivlin, Yossi Matias, Natalia Aizenberg\n", + "Abstract: Large language models hold significant promise in multilingual applications.\n", + "However, inherent biases stemming from predominantly English-centric\n", + "pre-training have led to the widespread practice of pre-translation, i.e.,\n", + "translating non-English inputs to English before inference, leading to\n", + "complexity and information loss. This study re-evaluates the need for\n", + "pre-translation in the context of PaLM2 models (Anil et al., 2023), which have\n", + "been established as highly performant in multilingual tasks. We offer a\n", + "comprehensive investigation across 108 languages and 6 diverse benchmarks,\n", + "including open-end generative tasks, which were excluded from previous similar\n", + "studies. Our findings challenge the pre-translation paradigm established in\n", + "prior research, highlighting the advantages of direct inference in PaLM2.\n", + "Specifically, PaLM2-L consistently outperforms pre-translation in 94 out of 108\n", + "languages. These findings pave the way for more efficient and effective\n", + "multilingual applications, alleviating the limitations associated with\n", + "pre-translation and unlocking linguistic authenticity.\n", + "Link: http://arxiv.org/abs/2403.04792v1\n", + "\n", + "---\n", + "\n", + "Title: SciAssess: Benchmarking LLM Proficiency in Scientific Literature\n", + " Analysis\n", + "Authors: Hengxing Cai, Xiaochen Cai, Junhan Chang, Sihang Li, Lin Yao, Changxin Wang, Zhifeng Gao, Hongshuai Wang, Yongge Li, Mujie Lin, Shuwen Yang, Jiankun Wang, Yuqi Yin, Yaqi Li, Linfeng Zhang, Guolin Ke\n", + "Abstract: Recent breakthroughs in Large Language Models (LLMs) have revolutionized\n", + "natural language understanding and generation, igniting a surge of interest in\n", + "leveraging these technologies in the field of scientific literature analysis.\n", + "Existing benchmarks, however, inadequately evaluate the proficiency of LLMs in\n", + "scientific literature analysis, especially in scenarios involving complex\n", + "comprehension and multimodal data. In response, we introduced SciAssess, a\n", + "benchmark tailored for the in-depth analysis of scientific literature, crafted\n", + "to provide a thorough assessment of LLMs' efficacy. SciAssess focuses on\n", + "evaluating LLMs' abilities in memorization, comprehension, and analysis within\n", + "the context of scientific literature analysis. It includes representative tasks\n", + "from diverse scientific fields, such as general chemistry, organic materials,\n", + "and alloy materials. And rigorous quality control measures ensure its\n", + "reliability in terms of correctness, anonymization, and copyright compliance.\n", + "SciAssess evaluates leading LLMs, including GPT-4, GPT-3.5, and Gemini,\n", + "identifying their strengths and aspects for improvement and supporting the\n", + "ongoing development of LLM applications in scientific literature analysis.\n", + "SciAssess and its resources are made available at https://sci-assess.github.io,\n", + "offering a valuable tool for advancing LLM capabilities in scientific\n", + "literature analysis.\n", + "Link: http://arxiv.org/abs/2403.01976v2\n", + "\n", + "---\n", + "\n", + "Title: Differentially Private Synthetic Data via Foundation Model APIs 2: Text\n", + "Authors: Chulin Xie, Zinan Lin, Arturs Backurs, Sivakanth Gopi, Da Yu, Huseyin A Inan, Harsha Nori, Haotian Jiang, Huishuai Zhang, Yin Tat Lee, Bo Li, Sergey Yekhanin\n", + "Abstract: Text data has become extremely valuable due to the emergence of machine\n", + "learning algorithms that learn from it. A lot of high-quality text data\n", + "generated in the real world is private and therefore cannot be shared or used\n", + "freely due to privacy concerns. Generating synthetic replicas of private text\n", + "data with a formal privacy guarantee, i.e., differential privacy (DP), offers a\n", + "promising and scalable solution. However, existing methods necessitate DP\n", + "finetuning of large language models (LLMs) on private data to generate DP\n", + "synthetic data. This approach is not viable for proprietary LLMs (e.g.,\n", + "GPT-3.5) and also demands considerable computational resources for open-source\n", + "LLMs. Lin et al. (2024) recently introduced the Private Evolution (PE)\n", + "algorithm to generate DP synthetic images with only API access to diffusion\n", + "models. In this work, we propose an augmented PE algorithm, named Aug-PE, that\n", + "applies to the complex setting of text. We use API access to an LLM and\n", + "generate DP synthetic text without any model training. We conduct comprehensive\n", + "experiments on three benchmark datasets. Our results demonstrate that Aug-PE\n", + "produces DP synthetic text that yields competitive utility with the SOTA DP\n", + "finetuning baselines. This underscores the feasibility of relying solely on API\n", + "access of LLMs to produce high-quality DP synthetic texts, thereby facilitating\n", + "more accessible routes to privacy-preserving LLM applications. Our code and\n", + "data are available at https://github.com/AI-secure/aug-pe.\n", + "Link: http://arxiv.org/abs/2403.01749v1\n", + "\n", + "---\n", + "\n", + "Title: SERVAL: Synergy Learning between Vertical Models and LLMs towards\n", + " Oracle-Level Zero-shot Medical Prediction\n", + "Authors: Jiahuan Yan, Jintai Chen, Chaowen Hu, Bo Zheng, Yaojun Hu, Jimeng Sun, Jian Wu\n", + "Abstract: Recent development of large language models (LLMs) has exhibited impressive\n", + "zero-shot proficiency on generic and common sense questions. However, LLMs'\n", + "application on domain-specific vertical questions still lags behind, primarily\n", + "due to the humiliation problems and deficiencies in vertical knowledge.\n", + "Furthermore, the vertical data annotation process often requires\n", + "labor-intensive expert involvement, thereby presenting an additional challenge\n", + "in enhancing the model's vertical capabilities. In this paper, we propose\n", + "SERVAL, a synergy learning pipeline designed for unsupervised development of\n", + "vertical capabilities in both LLMs and small models by mutual enhancement.\n", + "Specifically, SERVAL utilizes the LLM's zero-shot outputs as annotations,\n", + "leveraging its confidence to teach a robust vertical model from scratch.\n", + "Reversely, the trained vertical model guides the LLM fine-tuning to enhance its\n", + "zero-shot capability, progressively improving both models through an iterative\n", + "process. In medical domain, known for complex vertical knowledge and costly\n", + "annotations, comprehensive experiments show that, without access to any gold\n", + "labels, SERVAL with the synergy learning of OpenAI GPT-3.5 and a simple model\n", + "attains fully-supervised competitive performance across ten widely used medical\n", + "datasets. These datasets represent vertically specialized medical diagnostic\n", + "scenarios (e.g., diabetes, heart diseases, COVID-19), highlighting the\n", + "potential of SERVAL in refining the vertical capabilities of LLMs and training\n", + "vertical models from scratch, all achieved without the need for annotations.\n", + "Link: http://arxiv.org/abs/2403.01570v2\n", + "\n", + "---\n", "\n", "\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mResearch_Action_1\u001b[0m (to chat_manager):\n", "\n", - "Based on the retrieved abstracts, here is a markdown table categorizing the papers by domain, along with their titles, authors, summaries, and links:\n", + "Based on the provided code output, here is a markdown table categorizing the papers by domain, along with their titles, authors, summaries, and links:\n", "\n", "| Domain | Title | Authors | Summary | Link |\n", "|--------|-------|---------|---------|------|\n", - "| Security | PRSA: Prompt Reverse Stealing Attacks against Large Language Models | Yong Yang, Xuhong Zhang, Yi Jiang, Xi Chen, Haoyu Wang, Shouling Ji, Zonghui Wang | The paper explores the security risks associated with exposing input-output pairs of prompts used in LLMs and proposes a novel attack framework, PRSA, to reverse-steal prompts, posing a threat to intellectual property rights. | [Link](http://arxiv.org/abs/2402.19200v1) |\n", - "| Ethics & Evaluation | Political Compass or Spinning Arrow? Towards More Meaningful Evaluations for Values and Opinions in Large Language Models | Paul RΓΆttger, Valentin Hofmann, Valentina Pyatkin, Musashi Hinck, Hannah Rose Kirk, Hinrich SchΓΌtze, Dirk Hovy | This work challenges the constrained evaluation paradigm for values and opinions in LLMs and explores more realistic unconstrained evaluations, focusing on the Political Compass Test (PCT). | [Link](http://arxiv.org/abs/2402.16786v1) |\n", - "| Urban Mobility | Large Language Models as Urban Residents: An LLM Agent Framework for Personal Mobility Generation | Jiawei Wang, Renhe Jiang, Chuang Yang, Zengqing Wu, Makoto Onizuka, Ryosuke Shibasaki, Chuan Xiao | Introduces an LLM agent framework for personal mobility generation, aligning LLMs with real-world urban mobility data, and offering a tool for urban mobility analysis. | [Link](http://arxiv.org/abs/2402.14744v1) |\n", - "| Bioinformatics | An Evaluation of Large Language Models in Bioinformatics Research | Hengchuang Yin, Zhonghui Gu, Fanhao Wang, Yiparemu Abuduhaibaier, Yanqiao Zhu, Xinming Tu, Xian-Sheng Hua, Xiao Luo, Yizhou Sun | Evaluates the performance of LLMs on bioinformatics tasks, highlighting their potential and limitations, and motivating future research in LLM applications in bioinformatics. | [Link](http://arxiv.org/abs/2402.13714v1) |\n", - "| Privacy | Privacy-Preserving Instructions for Aligning Large Language Models | Da Yu, Peter Kairouz, Sewoong Oh, Zheng Xu | Proposes using synthetic instructions generated by privately fine-tuned generators to replace real instructions in data annotation and model fine-tuning, ensuring privacy while maintaining utility. | [Link](http://arxiv.org/abs/2402.13659v1) |\n", - "| Social Robotics | Ain't Misbehavin' -- Using LLMs to Generate Expressive Robot Behavior in Conversations with the Tabletop Robot Haru | Zining Wang, Paul Reisert, Eric Nichols, Randy Gomez | Integrates LLMs into social robots to generate dynamic and expressive conversations, using a text-to-speech engine and a library of physical actions for the robot. | [Link](http://arxiv.org/abs/2402.11571v1) |\n", - "| Ophthalmology | Fine-tuning Large Language Model (LLM) Artificial Intelligence Chatbots in Ophthalmology and LLM-based evaluation using GPT-4 | Ting Fang Tan, Kabilan Elangovan, Liyuan Jin, Yao Jie, Li Yong, Joshua Lim, Stanley Poh, Wei Yan Ng, Daniel Lim, Yuhe Ke, Nan Liu, Daniel Shu Wei Ting | Assesses the alignment of GPT-4-based evaluation to human clinician experts for evaluating responses to ophthalmology-related patient queries generated by fine-tuned LLM chatbots. | [Link](http://arxiv.org/abs/2402.10083v1) |\n", - "| Privacy & Data Security | Unmemorization in Large Language Models via Self-Distillation and Deliberate Imagination | Yijiang River Dong, Hongzhou Lin, Mikhail Belkin, Ramon Huerta, Ivan VuliΔ‡ | Introduces a novel approach for LLM unlearning by guiding LLMs to imagine alternative scenarios, effectively unlearning targeted text while preserving generation and NLU capabilities. | [Link](http://arxiv.org/abs/2402.10052v1) |\n", - "| Computational Efficiency | Anchor-based Large Language Models | Jianhui Pang, Fanghua Ye, Derek F. Wong, Longyue Wang | Proposes Anchor-based LLMs (AnLLMs) with an innovative anchor-based self-attention network (AnSAN) to reduce memory demand and enhance inference efficiency. | [Link](http://arxiv.org/abs/2402.07616v2) |\n", - "| Enterprise Applications | T-RAG: Lessons from the LLM Trenches | Masoomali Fatehkia, Ji Kim Lucas, Sanjay Chawla | Shares experiences building and deploying an LLM application for question answering over private organizational documents, combining RAG with a finetuned LLM and a tree structure for entity hierarchies. | [Link](http://arxiv.org/abs/2402.07483v1) |\n", - "\n", - "These papers cover a range of domains including security, ethics, urban mobility, bioinformatics, privacy, social robotics, ophthalmology, data security, computational efficiency, and enterprise applications, showcasing the diverse applications of large language models.\n", + "| Natural Language Processing | Adapting LLMs for Efficient Context Processing through Soft Prompt Compression | Cangqing Wang, et al. | The paper presents a framework for efficient context processing in LLMs using natural language summarization and soft prompt compression. | [Link](http://arxiv.org/abs/2404.04997v1) |\n", + "| Transportation | Explainable Traffic Flow Prediction with Large Language Models | Xusen Guo, et al. | This paper introduces a novel approach for interpretable traffic flow predictions using LLMs, which captures complex spatial-temporal patterns. | [Link](http://arxiv.org/abs/2404.02937v2) |\n", + "| Education | Designing Child-Centric AI Learning Environments: Insights from LLM-Enhanced Creative Project-Based Learning | Siyu Zha, et al. | The study explores the potential of LLMs in enhancing project-based learning (PBL) and fostering creativity in educational settings. | [Link](http://arxiv.org/abs/2403.16159v2) |\n", + "| Mental Health | The opportunities and risks of large language models in mental health | Hannah R. Lawrence, et al. | This review summarizes the literature on LLMs in mental health education, assessment, and intervention, highlighting opportunities and risks. | [Link](http://arxiv.org/abs/2403.14814v2) |\n", + "| Blockchain Security | Large Language Models for Blockchain Security: A Systematic Literature Review | Zheyuan He, et al. | The paper reviews the application of LLMs in blockchain security, discussing their impact and potential limitations. | [Link](http://arxiv.org/abs/2403.14280v2) |\n", + "| Spoken Language Understanding | Do Large Language Model Understand Multi-Intent Spoken Language? | Shangjian Yin, et al. | The study investigates LLMs' capabilities in multi-intent spoken language understanding and proposes new methodologies and metrics. | [Link](http://arxiv.org/abs/2403.04481v2) |\n", + "| Multilingualism | Breaking the Language Barrier: Can Direct Inference Outperform Pre-Translation in Multilingual LLM Applications? | Yotam Intrator, et al. | The paper challenges the pre-translation paradigm in multilingual LLM applications, showing the advantages of direct inference. | [Link](http://arxiv.org/abs/2403.04792v1) |\n", + "| Scientific Literature | SciAssess: Benchmarking LLM Proficiency in Scientific Literature Analysis | Hengxing Cai, et al. | Introduces SciAssess, a benchmark for evaluating LLMs' abilities in scientific literature analysis across various scientific fields. | [Link](http://arxiv.org/abs/2403.01976v2) |\n", + "| Privacy & Security | Differentially Private Synthetic Data via Foundation Model APIs 2: Text | Chulin Xie, et al. | The paper proposes a method to generate differentially private synthetic text data using API access to LLMs without model training. | [Link](http://arxiv.org/abs/2403.01749v1) |\n", + "| Medical Diagnostics | SERVAL: Synergy Learning between Vertical Models and LLMs towards Oracle-Level Zero-shot Medical Prediction | Jiahuan Yan, et al. | SERVAL is a synergy learning pipeline that enhances the vertical capabilities of LLMs and trains vertical models without annotations in the medical domain. | [Link](http://arxiv.org/abs/2403.01570v2) |\n", + "\n", + "Please note that the domains have been inferred from the summaries and titles of the papers and may not perfectly reflect the authors' intended categorization.\n", "\n", "--------------------------------------------------------------------------------\n" ] - }, - { - "data": { - "text/plain": [ - "ChatResult(chat_id=None, chat_history=[{'content': 'Topic: LLM applications papers from last week. Requirement: 5 - 10 papers from different domains.', 'role': 'assistant'}, {'content': 'To retrieve related papers from the arXiv API, we can use Python with the `requests` library to send a query to the API and parse the response. Below is a Python script that searches for papers related to \"LLM applications\" (Large Language Models applications) from the last week, across different domains, and prints out the required information for 5 to 10 papers.\\n\\n```python\\nimport requests\\nfrom datetime import datetime, timedelta\\nimport feedparser\\n\\n# Define the base URL for the arXiv API\\nARXIV_API_URL = \\'http://export.arxiv.org/api/query?\\'\\n\\n# Define the search parameters\\nsearch_query = \\'all:\"LLM applications\"\\'\\nstart_date = (datetime.now() - timedelta(days=7)).strftime(\\'%Y%m%d%H%M%S\\')\\nend_date = datetime.now().strftime(\\'%Y%m%d%H%M%S\\')\\nstart = 0\\nmax_results = 10\\nsort_by = \\'submittedDate\\'\\nsort_order = \\'descending\\'\\n\\n# Construct the query\\nquery = f\\'search_query={search_query}&sortBy={sort_by}&sortOrder={sort_order}&start={start}&max_results={max_results}\\'\\n\\n# Send the request to the arXiv API\\nresponse = requests.get(ARXIV_API_URL + query)\\n\\n# Parse the response using feedparser\\nfeed = feedparser.parse(response.content)\\n\\n# Print the title, authors, abstract, and link of each paper\\nfor entry in feed.entries:\\n print(\"Title:\", entry.title)\\n print(\"Authors:\", \\', \\'.join(author.name for author in entry.authors))\\n print(\"Abstract:\", entry.summary)\\n print(\"Link:\", entry.link)\\n print(\"\\\\n\")\\n\\n# Check if we have at least 5 papers, if not, adjust the search or notify\\nif len(feed.entries) < 5:\\n print(\"Less than 5 papers found. Consider adjusting the search parameters or timeframe.\")\\n```\\n\\nThis script will print the title, authors, abstract, and link for each paper related to \"LLM applications\" from the last week, up to a maximum of 10 papers. If fewer than 5 papers are found, it will notify the user to consider adjusting the search parameters or timeframe.', 'name': 'Retrieve_Action_1', 'role': 'user'}, {'content': \"exitcode: 0 (execution succeeded)\\nCode output: \\nTitle: PRSA: Prompt Reverse Stealing Attacks against Large Language Models\\nAuthors: Yong Yang, Xuhong Zhang, Yi Jiang, Xi Chen, Haoyu Wang, Shouling Ji, Zonghui Wang\\nAbstract: Prompt, recognized as crucial intellectual property, enables large language\\nmodels (LLMs) to perform specific tasks without the need of fine-tuning,\\nunderscoring their escalating importance. With the rise of prompt-based\\nservices, such as prompt marketplaces and LLM applications, providers often\\ndisplay prompts' capabilities through input-output examples to attract users.\\nHowever, this paradigm raises a pivotal security concern: does the exposure of\\ninput-output pairs pose the risk of potential prompt leakage, infringing on the\\nintellectual property rights of the developers? To our knowledge, this problem\\nstill has not been comprehensively explored yet. To remedy this gap, in this\\npaper, we perform the first in depth exploration and propose a novel attack\\nframework for reverse-stealing prompts against commercial LLMs, namely PRSA.\\nThe main idea of PRSA is that by analyzing the critical features of the\\ninput-output pairs, we mimic and gradually infer (steal) the target prompts. In\\ndetail, PRSA mainly consists of two key phases: prompt mutation and prompt\\npruning. In the mutation phase, we propose a prompt attention algorithm based\\non differential feedback to capture these critical features for effectively\\ninferring the target prompts. In the prompt pruning phase, we identify and mask\\nthe words dependent on specific inputs, enabling the prompts to accommodate\\ndiverse inputs for generalization. Through extensive evaluation, we verify that\\nPRSA poses a severe threat in real world scenarios. We have reported these\\nfindings to prompt service providers and actively collaborate with them to take\\nprotective measures for prompt copyright.\\nLink: http://arxiv.org/abs/2402.19200v1\\n\\n\\nTitle: Political Compass or Spinning Arrow? Towards More Meaningful Evaluations\\n for Values and Opinions in Large Language Models\\nAuthors: Paul RΓΆttger, Valentin Hofmann, Valentina Pyatkin, Musashi Hinck, Hannah Rose Kirk, Hinrich SchΓΌtze, Dirk Hovy\\nAbstract: Much recent work seeks to evaluate values and opinions in large language\\nmodels (LLMs) using multiple-choice surveys and questionnaires. Most of this\\nwork is motivated by concerns around real-world LLM applications. For example,\\npolitically-biased LLMs may subtly influence society when they are used by\\nmillions of people. Such real-world concerns, however, stand in stark contrast\\nto the artificiality of current evaluations: real users do not typically ask\\nLLMs survey questions. Motivated by this discrepancy, we challenge the\\nprevailing constrained evaluation paradigm for values and opinions in LLMs and\\nexplore more realistic unconstrained evaluations. As a case study, we focus on\\nthe popular Political Compass Test (PCT). In a systematic review, we find that\\nmost prior work using the PCT forces models to comply with the PCT's\\nmultiple-choice format. We show that models give substantively different\\nanswers when not forced; that answers change depending on how models are\\nforced; and that answers lack paraphrase robustness. Then, we demonstrate that\\nmodels give different answers yet again in a more realistic open-ended answer\\nsetting. We distill these findings into recommendations and open challenges in\\nevaluating values and opinions in LLMs.\\nLink: http://arxiv.org/abs/2402.16786v1\\n\\n\\nTitle: Large Language Models as Urban Residents: An LLM Agent Framework for\\n Personal Mobility Generation\\nAuthors: Jiawei Wang, Renhe Jiang, Chuang Yang, Zengqing Wu, Makoto Onizuka, Ryosuke Shibasaki, Chuan Xiao\\nAbstract: This paper introduces a novel approach using Large Language Models (LLMs)\\nintegrated into an agent framework for flexible and efficient personal mobility\\ngeneration. LLMs overcome the limitations of previous models by efficiently\\nprocessing semantic data and offering versatility in modeling various tasks.\\nOur approach addresses the critical need to align LLMs with real-world urban\\nmobility data, focusing on three research questions: aligning LLMs with rich\\nactivity data, developing reliable activity generation strategies, and\\nexploring LLM applications in urban mobility. The key technical contribution is\\na novel LLM agent framework that accounts for individual activity patterns and\\nmotivations, including a self-consistency approach to align LLMs with\\nreal-world activity data and a retrieval-augmented strategy for interpretable\\nactivity generation. In experimental studies, comprehensive validation is\\nperformed using real-world data. This research marks the pioneering work of\\ndesigning an LLM agent framework for activity generation based on real-world\\nhuman activity data, offering a promising tool for urban mobility analysis.\\nLink: http://arxiv.org/abs/2402.14744v1\\n\\n\\nTitle: An Evaluation of Large Language Models in Bioinformatics Research\\nAuthors: Hengchuang Yin, Zhonghui Gu, Fanhao Wang, Yiparemu Abuduhaibaier, Yanqiao Zhu, Xinming Tu, Xian-Sheng Hua, Xiao Luo, Yizhou Sun\\nAbstract: Large language models (LLMs) such as ChatGPT have gained considerable\\ninterest across diverse research communities. Their notable ability for text\\ncompletion and generation has inaugurated a novel paradigm for\\nlanguage-interfaced problem solving. However, the potential and efficacy of\\nthese models in bioinformatics remain incompletely explored. In this work, we\\nstudy the performance LLMs on a wide spectrum of crucial bioinformatics tasks.\\nThese tasks include the identification of potential coding regions, extraction\\nof named entities for genes and proteins, detection of antimicrobial and\\nanti-cancer peptides, molecular optimization, and resolution of educational\\nbioinformatics problems. Our findings indicate that, given appropriate prompts,\\nLLMs like GPT variants can successfully handle most of these tasks. In\\naddition, we provide a thorough analysis of their limitations in the context of\\ncomplicated bioinformatics tasks. In conclusion, we believe that this work can\\nprovide new perspectives and motivate future research in the field of LLMs\\napplications, AI for Science and bioinformatics.\\nLink: http://arxiv.org/abs/2402.13714v1\\n\\n\\nTitle: Privacy-Preserving Instructions for Aligning Large Language Models\\nAuthors: Da Yu, Peter Kairouz, Sewoong Oh, Zheng Xu\\nAbstract: Service providers of large language model (LLM) applications collect user\\ninstructions in the wild and use them in further aligning LLMs with users'\\nintentions. These instructions, which potentially contain sensitive\\ninformation, are annotated by human workers in the process. This poses a new\\nprivacy risk not addressed by the typical private optimization. To this end, we\\npropose using synthetic instructions to replace real instructions in data\\nannotation and model fine-tuning. Formal differential privacy is guaranteed by\\ngenerating those synthetic instructions using privately fine-tuned generators.\\nCrucial in achieving the desired utility is our novel filtering algorithm that\\nmatches the distribution of the synthetic instructions to that of the real\\nones. In both supervised fine-tuning and reinforcement learning from human\\nfeedback, our extensive experiments demonstrate the high utility of the final\\nset of synthetic instructions by showing comparable results to real\\ninstructions. In supervised fine-tuning, models trained with private synthetic\\ninstructions outperform leading open-source models such as Vicuna.\\nLink: http://arxiv.org/abs/2402.13659v1\\n\\n\\nTitle: Ain't Misbehavin' -- Using LLMs to Generate Expressive Robot Behavior in\\n Conversations with the Tabletop Robot Haru\\nAuthors: Zining Wang, Paul Reisert, Eric Nichols, Randy Gomez\\nAbstract: Social robots aim to establish long-term bonds with humans through engaging\\nconversation. However, traditional conversational approaches, reliant on\\nscripted interactions, often fall short in maintaining engaging conversations.\\nThis paper addresses this limitation by integrating large language models\\n(LLMs) into social robots to achieve more dynamic and expressive conversations.\\nWe introduce a fully-automated conversation system that leverages LLMs to\\ngenerate robot responses with expressive behaviors, congruent with the robot's\\npersonality. We incorporate robot behavior with two modalities: 1) a\\ntext-to-speech (TTS) engine capable of various delivery styles, and 2) a\\nlibrary of physical actions for the robot. We develop a custom,\\nstate-of-the-art emotion recognition model to dynamically select the robot's\\ntone of voice and utilize emojis from LLM output as cues for generating robot\\nactions. A demo of our system is available here. To illuminate design and\\nimplementation issues, we conduct a pilot study where volunteers chat with a\\nsocial robot using our proposed system, and we analyze their feedback,\\nconducting a rigorous error analysis of chat transcripts. Feedback was\\noverwhelmingly positive, with participants commenting on the robot's empathy,\\nhelpfulness, naturalness, and entertainment. Most negative feedback was due to\\nautomatic speech recognition (ASR) errors which had limited impact on\\nconversations. However, we observed a small class of errors, such as the LLM\\nrepeating itself or hallucinating fictitious information and human responses,\\nthat have the potential to derail conversations, raising important issues for\\nLLM application.\\nLink: http://arxiv.org/abs/2402.11571v1\\n\\n\\nTitle: Fine-tuning Large Language Model (LLM) Artificial Intelligence Chatbots\\n in Ophthalmology and LLM-based evaluation using GPT-4\\nAuthors: Ting Fang Tan, Kabilan Elangovan, Liyuan Jin, Yao Jie, Li Yong, Joshua Lim, Stanley Poh, Wei Yan Ng, Daniel Lim, Yuhe Ke, Nan Liu, Daniel Shu Wei Ting\\nAbstract: Purpose: To assess the alignment of GPT-4-based evaluation to human clinician\\nexperts, for the evaluation of responses to ophthalmology-related patient\\nqueries generated by fine-tuned LLM chatbots. Methods: 400 ophthalmology\\nquestions and paired answers were created by ophthalmologists to represent\\ncommonly asked patient questions, divided into fine-tuning (368; 92%), and\\ntesting (40; 8%). We find-tuned 5 different LLMs, including LLAMA2-7b,\\nLLAMA2-7b-Chat, LLAMA2-13b, and LLAMA2-13b-Chat. For the testing dataset,\\nadditional 8 glaucoma QnA pairs were included. 200 responses to the testing\\ndataset were generated by 5 fine-tuned LLMs for evaluation. A customized\\nclinical evaluation rubric was used to guide GPT-4 evaluation, grounded on\\nclinical accuracy, relevance, patient safety, and ease of understanding. GPT-4\\nevaluation was then compared against ranking by 5 clinicians for clinical\\nalignment. Results: Among all fine-tuned LLMs, GPT-3.5 scored the highest\\n(87.1%), followed by LLAMA2-13b (80.9%), LLAMA2-13b-chat (75.5%),\\nLLAMA2-7b-Chat (70%) and LLAMA2-7b (68.8%) based on the GPT-4 evaluation. GPT-4\\nevaluation demonstrated significant agreement with human clinician rankings,\\nwith Spearman and Kendall Tau correlation coefficients of 0.90 and 0.80\\nrespectively; while correlation based on Cohen Kappa was more modest at 0.50.\\nNotably, qualitative analysis and the glaucoma sub-analysis revealed clinical\\ninaccuracies in the LLM-generated responses, which were appropriately\\nidentified by the GPT-4 evaluation. Conclusion: The notable clinical alignment\\nof GPT-4 evaluation highlighted its potential to streamline the clinical\\nevaluation of LLM chatbot responses to healthcare-related queries. By\\ncomplementing the existing clinician-dependent manual grading, this efficient\\nand automated evaluation could assist the validation of future developments in\\nLLM applications for healthcare.\\nLink: http://arxiv.org/abs/2402.10083v1\\n\\n\\nTitle: Unmemorization in Large Language Models via Self-Distillation and\\n Deliberate Imagination\\nAuthors: Yijiang River Dong, Hongzhou Lin, Mikhail Belkin, Ramon Huerta, Ivan VuliΔ‡\\nAbstract: While displaying impressive generation capabilities across many tasks, Large\\nLanguage Models (LLMs) still struggle with crucial issues of privacy violation\\nand unwanted exposure of sensitive data. This raises an essential question: how\\nshould we prevent such undesired behavior of LLMs while maintaining their\\nstrong generation and natural language understanding (NLU) capabilities? In\\nthis work, we introduce a novel approach termed deliberate imagination in the\\ncontext of LLM unlearning. Instead of trying to forget memorized data, we\\nemploy a self-distillation framework, guiding LLMs to deliberately imagine\\nalternative scenarios. As demonstrated in a wide range of experiments, the\\nproposed method not only effectively unlearns targeted text but also preserves\\nthe LLMs' capabilities in open-ended generation tasks as well as in NLU tasks.\\nOur results demonstrate the usefulness of this approach across different models\\nand sizes, and also with parameter-efficient fine-tuning, offering a novel\\npathway to addressing the challenges with private and sensitive data in LLM\\napplications.\\nLink: http://arxiv.org/abs/2402.10052v1\\n\\n\\nTitle: Anchor-based Large Language Models\\nAuthors: Jianhui Pang, Fanghua Ye, Derek F. Wong, Longyue Wang\\nAbstract: Large language models (LLMs) predominantly employ decoder-only transformer\\narchitectures, necessitating the retention of keys/values information for\\nhistorical tokens to provide contextual information and avoid redundant\\ncomputation. However, the substantial size and parameter volume of these LLMs\\nrequire massive GPU memory. This memory demand increases with the length of the\\ninput text, leading to an urgent need for more efficient methods of information\\nstorage and processing. This study introduces Anchor-based LLMs (AnLLMs), which\\nutilize an innovative anchor-based self-attention network (AnSAN) and also an\\nanchor-based inference strategy. This approach enables LLMs to compress\\nsequence information into an anchor token, reducing the keys/values cache and\\nenhancing inference efficiency. Experiments on question-answering benchmarks\\nreveal that AnLLMs maintain similar accuracy levels while achieving up to 99%\\nkeys/values cache reduction and up to 3.5 times faster inference. Despite a\\nminor compromise in accuracy, the substantial enhancements of AnLLMs employing\\nthe AnSAN technique in resource utilization and computational efficiency\\nunderscore their potential for practical LLM applications.\\nLink: http://arxiv.org/abs/2402.07616v2\\n\\n\\nTitle: T-RAG: Lessons from the LLM Trenches\\nAuthors: Masoomali Fatehkia, Ji Kim Lucas, Sanjay Chawla\\nAbstract: Large Language Models (LLM) have shown remarkable language capabilities\\nfueling attempts to integrate them into applications across a wide range of\\ndomains. An important application area is question answering over private\\nenterprise documents where the main considerations are data security, which\\nnecessitates applications that can be deployed on-prem, limited computational\\nresources and the need for a robust application that correctly responds to\\nqueries. Retrieval-Augmented Generation (RAG) has emerged as the most prominent\\nframework for building LLM-based applications. While building a RAG is\\nrelatively straightforward, making it robust and a reliable application\\nrequires extensive customization and relatively deep knowledge of the\\napplication domain. We share our experiences building and deploying an LLM\\napplication for question answering over private organizational documents. Our\\napplication combines the use of RAG with a finetuned open-source LLM.\\nAdditionally, our system, which we call Tree-RAG (T-RAG), uses a tree structure\\nto represent entity hierarchies within the organization. This is used to\\ngenerate a textual description to augment the context when responding to user\\nqueries pertaining to entities within the organization's hierarchy. Our\\nevaluations show that this combination performs better than a simple RAG or\\nfinetuning implementation. Finally, we share some lessons learned based on our\\nexperiences building an LLM application for real-world use.\\nLink: http://arxiv.org/abs/2402.07483v1\\n\\n\\n\", 'name': 'Retrieve_Action_2', 'role': 'user'}, {'content': \"Based on the retrieved abstracts, here is a markdown table categorizing the papers by domain, along with their titles, authors, summaries, and links:\\n\\n| Domain | Title | Authors | Summary | Link |\\n|--------|-------|---------|---------|------|\\n| Security | PRSA: Prompt Reverse Stealing Attacks against Large Language Models | Yong Yang, Xuhong Zhang, Yi Jiang, Xi Chen, Haoyu Wang, Shouling Ji, Zonghui Wang | The paper explores the security risks associated with exposing input-output pairs of prompts used in LLMs and proposes a novel attack framework, PRSA, to reverse-steal prompts, posing a threat to intellectual property rights. | [Link](http://arxiv.org/abs/2402.19200v1) |\\n| Ethics & Evaluation | Political Compass or Spinning Arrow? Towards More Meaningful Evaluations for Values and Opinions in Large Language Models | Paul RΓΆttger, Valentin Hofmann, Valentina Pyatkin, Musashi Hinck, Hannah Rose Kirk, Hinrich SchΓΌtze, Dirk Hovy | This work challenges the constrained evaluation paradigm for values and opinions in LLMs and explores more realistic unconstrained evaluations, focusing on the Political Compass Test (PCT). | [Link](http://arxiv.org/abs/2402.16786v1) |\\n| Urban Mobility | Large Language Models as Urban Residents: An LLM Agent Framework for Personal Mobility Generation | Jiawei Wang, Renhe Jiang, Chuang Yang, Zengqing Wu, Makoto Onizuka, Ryosuke Shibasaki, Chuan Xiao | Introduces an LLM agent framework for personal mobility generation, aligning LLMs with real-world urban mobility data, and offering a tool for urban mobility analysis. | [Link](http://arxiv.org/abs/2402.14744v1) |\\n| Bioinformatics | An Evaluation of Large Language Models in Bioinformatics Research | Hengchuang Yin, Zhonghui Gu, Fanhao Wang, Yiparemu Abuduhaibaier, Yanqiao Zhu, Xinming Tu, Xian-Sheng Hua, Xiao Luo, Yizhou Sun | Evaluates the performance of LLMs on bioinformatics tasks, highlighting their potential and limitations, and motivating future research in LLM applications in bioinformatics. | [Link](http://arxiv.org/abs/2402.13714v1) |\\n| Privacy | Privacy-Preserving Instructions for Aligning Large Language Models | Da Yu, Peter Kairouz, Sewoong Oh, Zheng Xu | Proposes using synthetic instructions generated by privately fine-tuned generators to replace real instructions in data annotation and model fine-tuning, ensuring privacy while maintaining utility. | [Link](http://arxiv.org/abs/2402.13659v1) |\\n| Social Robotics | Ain't Misbehavin' -- Using LLMs to Generate Expressive Robot Behavior in Conversations with the Tabletop Robot Haru | Zining Wang, Paul Reisert, Eric Nichols, Randy Gomez | Integrates LLMs into social robots to generate dynamic and expressive conversations, using a text-to-speech engine and a library of physical actions for the robot. | [Link](http://arxiv.org/abs/2402.11571v1) |\\n| Ophthalmology | Fine-tuning Large Language Model (LLM) Artificial Intelligence Chatbots in Ophthalmology and LLM-based evaluation using GPT-4 | Ting Fang Tan, Kabilan Elangovan, Liyuan Jin, Yao Jie, Li Yong, Joshua Lim, Stanley Poh, Wei Yan Ng, Daniel Lim, Yuhe Ke, Nan Liu, Daniel Shu Wei Ting | Assesses the alignment of GPT-4-based evaluation to human clinician experts for evaluating responses to ophthalmology-related patient queries generated by fine-tuned LLM chatbots. | [Link](http://arxiv.org/abs/2402.10083v1) |\\n| Privacy & Data Security | Unmemorization in Large Language Models via Self-Distillation and Deliberate Imagination | Yijiang River Dong, Hongzhou Lin, Mikhail Belkin, Ramon Huerta, Ivan VuliΔ‡ | Introduces a novel approach for LLM unlearning by guiding LLMs to imagine alternative scenarios, effectively unlearning targeted text while preserving generation and NLU capabilities. | [Link](http://arxiv.org/abs/2402.10052v1) |\\n| Computational Efficiency | Anchor-based Large Language Models | Jianhui Pang, Fanghua Ye, Derek F. Wong, Longyue Wang | Proposes Anchor-based LLMs (AnLLMs) with an innovative anchor-based self-attention network (AnSAN) to reduce memory demand and enhance inference efficiency. | [Link](http://arxiv.org/abs/2402.07616v2) |\\n| Enterprise Applications | T-RAG: Lessons from the LLM Trenches | Masoomali Fatehkia, Ji Kim Lucas, Sanjay Chawla | Shares experiences building and deploying an LLM application for question answering over private organizational documents, combining RAG with a finetuned LLM and a tree structure for entity hierarchies. | [Link](http://arxiv.org/abs/2402.07483v1) |\\n\\nThese papers cover a range of domains including security, ethics, urban mobility, bioinformatics, privacy, social robotics, ophthalmology, data security, computational efficiency, and enterprise applications, showcasing the diverse applications of large language models.\", 'name': 'Research_Action_1', 'role': 'user'}], summary=\"Based on the retrieved abstracts, here is a markdown table categorizing the papers by domain, along with their titles, authors, summaries, and links:\\n\\n| Domain | Title | Authors | Summary | Link |\\n|--------|-------|---------|---------|------|\\n| Security | PRSA: Prompt Reverse Stealing Attacks against Large Language Models | Yong Yang, Xuhong Zhang, Yi Jiang, Xi Chen, Haoyu Wang, Shouling Ji, Zonghui Wang | The paper explores the security risks associated with exposing input-output pairs of prompts used in LLMs and proposes a novel attack framework, PRSA, to reverse-steal prompts, posing a threat to intellectual property rights. | [Link](http://arxiv.org/abs/2402.19200v1) |\\n| Ethics & Evaluation | Political Compass or Spinning Arrow? Towards More Meaningful Evaluations for Values and Opinions in Large Language Models | Paul RΓΆttger, Valentin Hofmann, Valentina Pyatkin, Musashi Hinck, Hannah Rose Kirk, Hinrich SchΓΌtze, Dirk Hovy | This work challenges the constrained evaluation paradigm for values and opinions in LLMs and explores more realistic unconstrained evaluations, focusing on the Political Compass Test (PCT). | [Link](http://arxiv.org/abs/2402.16786v1) |\\n| Urban Mobility | Large Language Models as Urban Residents: An LLM Agent Framework for Personal Mobility Generation | Jiawei Wang, Renhe Jiang, Chuang Yang, Zengqing Wu, Makoto Onizuka, Ryosuke Shibasaki, Chuan Xiao | Introduces an LLM agent framework for personal mobility generation, aligning LLMs with real-world urban mobility data, and offering a tool for urban mobility analysis. | [Link](http://arxiv.org/abs/2402.14744v1) |\\n| Bioinformatics | An Evaluation of Large Language Models in Bioinformatics Research | Hengchuang Yin, Zhonghui Gu, Fanhao Wang, Yiparemu Abuduhaibaier, Yanqiao Zhu, Xinming Tu, Xian-Sheng Hua, Xiao Luo, Yizhou Sun | Evaluates the performance of LLMs on bioinformatics tasks, highlighting their potential and limitations, and motivating future research in LLM applications in bioinformatics. | [Link](http://arxiv.org/abs/2402.13714v1) |\\n| Privacy | Privacy-Preserving Instructions for Aligning Large Language Models | Da Yu, Peter Kairouz, Sewoong Oh, Zheng Xu | Proposes using synthetic instructions generated by privately fine-tuned generators to replace real instructions in data annotation and model fine-tuning, ensuring privacy while maintaining utility. | [Link](http://arxiv.org/abs/2402.13659v1) |\\n| Social Robotics | Ain't Misbehavin' -- Using LLMs to Generate Expressive Robot Behavior in Conversations with the Tabletop Robot Haru | Zining Wang, Paul Reisert, Eric Nichols, Randy Gomez | Integrates LLMs into social robots to generate dynamic and expressive conversations, using a text-to-speech engine and a library of physical actions for the robot. | [Link](http://arxiv.org/abs/2402.11571v1) |\\n| Ophthalmology | Fine-tuning Large Language Model (LLM) Artificial Intelligence Chatbots in Ophthalmology and LLM-based evaluation using GPT-4 | Ting Fang Tan, Kabilan Elangovan, Liyuan Jin, Yao Jie, Li Yong, Joshua Lim, Stanley Poh, Wei Yan Ng, Daniel Lim, Yuhe Ke, Nan Liu, Daniel Shu Wei Ting | Assesses the alignment of GPT-4-based evaluation to human clinician experts for evaluating responses to ophthalmology-related patient queries generated by fine-tuned LLM chatbots. | [Link](http://arxiv.org/abs/2402.10083v1) |\\n| Privacy & Data Security | Unmemorization in Large Language Models via Self-Distillation and Deliberate Imagination | Yijiang River Dong, Hongzhou Lin, Mikhail Belkin, Ramon Huerta, Ivan VuliΔ‡ | Introduces a novel approach for LLM unlearning by guiding LLMs to imagine alternative scenarios, effectively unlearning targeted text while preserving generation and NLU capabilities. | [Link](http://arxiv.org/abs/2402.10052v1) |\\n| Computational Efficiency | Anchor-based Large Language Models | Jianhui Pang, Fanghua Ye, Derek F. Wong, Longyue Wang | Proposes Anchor-based LLMs (AnLLMs) with an innovative anchor-based self-attention network (AnSAN) to reduce memory demand and enhance inference efficiency. | [Link](http://arxiv.org/abs/2402.07616v2) |\\n| Enterprise Applications | T-RAG: Lessons from the LLM Trenches | Masoomali Fatehkia, Ji Kim Lucas, Sanjay Chawla | Shares experiences building and deploying an LLM application for question answering over private organizational documents, combining RAG with a finetuned LLM and a tree structure for entity hierarchies. | [Link](http://arxiv.org/abs/2402.07483v1) |\\n\\nThese papers cover a range of domains including security, ethics, urban mobility, bioinformatics, privacy, social robotics, ophthalmology, data security, computational efficiency, and enterprise applications, showcasing the diverse applications of large language models.\", cost=({'total_cost': 0}, {'total_cost': 0}), human_input=[])" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" } ], "source": [ - "initializer.initiate_chat(\n", + "chat_result = initializer.initiate_chat(\n", " manager, message=\"Topic: LLM applications papers from last week. Requirement: 5 - 10 papers from different domains.\"\n", ")" ] diff --git a/notebook/agentchat_image_generation_capability.ipynb b/notebook/agentchat_image_generation_capability.ipynb index 0c2e077a378..b5d298d7f4d 100644 --- a/notebook/agentchat_image_generation_capability.ipynb +++ b/notebook/agentchat_image_generation_capability.ipynb @@ -5,15 +5,7 @@ "id": "a1bbe66b-3784-4da2-a5ca-5406a4d0c843", "metadata": {}, "source": [ - "# Adding Different LLM Modalities to Exisiting Agents" - ] - }, - { - "cell_type": "markdown", - "id": "c1742c7a-2db4-49a0-bc6a-0500a4fa6af3", - "metadata": {}, - "source": [ - "### This notebook showcases how to add image generation modality as a conversable agent capability " + "# Generate Dalle Images With Conversable Agents" ] }, { @@ -21,20 +13,20 @@ "id": "fa341ad5-32f0-4e4b-8869-e19f19471d81", "metadata": {}, "source": [ - "We first need to make sure you have the right dependencies installed.\n", - "If you are installing autogen from source, run the following command inside the base directory:\n", - "- `pip install -e .[lmm]`\n", + "This notebook illustrates how to add the image generation capability to a conversable agent. \n", "\n", - "If you're not installing autogen from source, run the following command:\n", - "- `pip install \"pyautogen[lmm]\"`" - ] - }, - { - "cell_type": "markdown", - "id": "4017e450-b4da-4bd2-b4ff-91bccd664284", - "metadata": {}, - "source": [ - "First lets import all the required modules to run this example" + "````{=mdx}\n", + ":::info Requirements\n", + "Some extra dependencies are needed for this notebook, which can be installed via pip:\n", + "\n", + "```bash\n", + "pip install pyautogen[lmm]\n", + "```\n", + "\n", + "For more information, please refer to the [installation guide](/docs/installation/).\n", + ":::\n", + "````\n", + "First, let's import all the required modules to run this example." ] }, { @@ -48,14 +40,14 @@ "import re\n", "from typing import Dict, Optional\n", "\n", + "from IPython.display import display\n", "from PIL.Image import Image\n", "\n", "import autogen\n", - "from autogen.cache import Cache\n", "from autogen.agentchat.contrib import img_utils\n", "from autogen.agentchat.contrib.capabilities import generate_images\n", - "from autogen.oai import openai_utils\n", - "from IPython.display import display" + "from autogen.cache import Cache\n", + "from autogen.oai import openai_utils" ] }, { @@ -63,12 +55,12 @@ "id": "156c6db3-fb95-4ff4-803b-81c105a095e2", "metadata": {}, "source": [ - "Let's define our LLM configs (you can experiment with different params)" + "Let's define our LLM configs." ] }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 2, "id": "104db7e8-e30a-4012-916d-5f1622e5da02", "metadata": {}, "outputs": [], @@ -84,7 +76,7 @@ " \"temperature\": 0.7,\n", "}\n", "dalle_config = {\n", - " \"config_list\": [{\"model\": \"dall-e-3\", \"api_key\": os.environ[\"OPAI_API_KEY\"]}],\n", + " \"config_list\": [{\"model\": \"dall-e-3\", \"api_key\": os.environ[\"OPENAI_API_KEY\"]}],\n", " \"timeout\": 120,\n", " \"temperature\": 0.7,\n", "}" @@ -95,36 +87,55 @@ "id": "57cec727-c237-4205-8681-e8a792f0a242", "metadata": {}, "source": [ + "````{=mdx}\n", + ":::tip\n", + "Learn more about configuring LLMs for agents [here](/docs/topics/llm_configuration).\n", + ":::\n", + "````\n", + "\n", "Our system will consist of 2 main agents:\n", - "1. Image generator agent\n", - "2. Critic agent\n", + "1. Image generator agent.\n", + "2. Critic agent.\n", "\n", "The image generator agent will carry a conversation with the critic, and generate images based on the critic's requests." ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "id": "132e57ea-a041-4c4d-99ca-b0616505b6be", "metadata": {}, "outputs": [], "source": [ "CRITIC_SYSTEM_MESSAGE = \"\"\"You need to improve the prompt of the figures you saw.\n", - "How to create a figure that is better in terms of color, shape, text (clarity), and other things.\n", + "How to create an image that is better in terms of color, shape, text (clarity), and other things.\n", "Reply with the following format:\n", "\n", "CRITICS: the image needs to improve...\n", "PROMPT: here is the updated prompt!\n", + "\n", + "If you have no critique or a prompt, just say TERMINATE\n", "\"\"\"" ] }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 4, "id": "427233a0-190b-4acd-80a3-c5951609b337", "metadata": {}, "outputs": [], "source": [ + "def _is_termination_message(msg) -> bool:\n", + " # Detects if we should terminate the conversation\n", + " if isinstance(msg.get(\"content\"), str):\n", + " return msg[\"content\"].rstrip().endswith(\"TERMINATE\")\n", + " elif isinstance(msg.get(\"content\"), list):\n", + " for content in msg[\"content\"]:\n", + " if isinstance(content, dict) and \"text\" in content:\n", + " return content[\"text\"].rstrip().endswith(\"TERMINATE\")\n", + " return False\n", + "\n", + "\n", "def critic_agent() -> autogen.ConversableAgent:\n", " return autogen.ConversableAgent(\n", " name=\"critic\",\n", @@ -132,20 +143,24 @@ " system_message=CRITIC_SYSTEM_MESSAGE,\n", " max_consecutive_auto_reply=3,\n", " human_input_mode=\"NEVER\",\n", + " is_termination_msg=lambda msg: _is_termination_message(msg),\n", " )\n", "\n", "\n", "def image_generator_agent() -> autogen.ConversableAgent:\n", " # Create the agent\n", " agent = autogen.ConversableAgent(\n", - " name=\"dalle\", llm_config=gpt_vision_config, max_consecutive_auto_reply=3, human_input_mode=\"NEVER\"\n", + " name=\"dalle\",\n", + " llm_config=gpt_vision_config,\n", + " max_consecutive_auto_reply=3,\n", + " human_input_mode=\"NEVER\",\n", + " is_termination_msg=lambda msg: _is_termination_message(msg),\n", " )\n", - " cache = Cache.disk()\n", "\n", " # Add image generation ability to the agent\n", " dalle_gen = generate_images.DalleImageGenerator(llm_config=dalle_config)\n", " image_gen_capability = generate_images.ImageGeneration(\n", - " image_generator=dalle_gen, cache=cache, text_analyzer_llm_config=gpt_config()\n", + " image_generator=dalle_gen, text_analyzer_llm_config=gpt_config\n", " )\n", "\n", " image_gen_capability.add_to_agent(agent)\n", @@ -162,7 +177,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "id": "ff882a30-0f1f-4c6a-8951-9e11df48bc26", "metadata": {}, "outputs": [], @@ -197,7 +212,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 6, "id": "ed01470b-ad59-483d-990e-772d27e0764a", "metadata": {}, "outputs": [ @@ -207,41 +222,27 @@ "text": [ "\u001b[33mdalle\u001b[0m (to critic):\n", "\n", - "a happy dog wearing a shirt saying 'I Love AutoGen', make sure the text is clear\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mcritic\u001b[0m (to dalle):\n", - "\n", - "CRITICS: The image needs to improve on the color contrast between the text on the shirt and the shirt itself to ensure the message 'I Love AutoGen' stands out. The shape of the dog should be distinct and recognizable as a happy dog. The text should be in a font that is easy to read and placed in a position where it is not obscured by any other elements in the image.\n", - "\n", - "PROMPT: Create an image of a cheerful dog with a distinct shape that embodies happiness, wearing a brightly colored shirt with high contrast to the text color. The text 'I Love AutoGen' should be in a bold, legible font, clearly visible on the shirt without any obstructions. Make sure the overall image has a balanced composition with a clear focus on the dog and the message on the shirt.\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mdalle\u001b[0m (to critic):\n", - "\n", - "i generated an image with the prompt: Cheerful dog with a distinct, recognizable shape that embodies happiness, wearing a brightly colored shirt. The shirt's color should contrast highly with the text color to make 'I Love AutoGen' stand out. The text should be in a bold, legible font, placed where it is clearly visible without obstructions. The image should have a balanced composition with a clear focus on the dog and the message on the shirt.\n", + "A happy dog wearing a shirt saying 'I Love AutoGen'. Make sure the text is clear.\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mcritic\u001b[0m (to dalle):\n", "\n", - "CRITICS: The image successfully captures the cheerful demeanor of the dog, and the color of the shirt stands out well. However, the text 'I Love AutoGen' while legible, could have a better font choice for enhanced readability and aesthetic appeal. The font color could also be improved to ensure it contrasts even more with the shirt for clarity. Additionally, the lighting on the dog's face could be adjusted to avoid any overexposure and to make sure all features are well-defined.\n", + "CRITICS: the image needs to improve the contrast and size of the text to enhance its clarity, and the shirt's color should not clash with the dog's fur color to maintain a harmonious color scheme.\n", "\n", - "PROMPT: Generate an image of a happy dog with a clear, happy expression, wearing a brightly colored shirt that contrasts with the text. Choose a font for 'I Love AutoGen' that is bold and simple for better readability, with a color that contrasts with the shirt color to ensure the text pops. Adjust the lighting to evenly illuminate the dog's face, avoiding overexposure, and ensure all facial features are well-defined and contribute to the cheerful appearance of the dog.\n", + "PROMPT: here is the updated prompt!\n", + "Create an image of a joyful dog with a coat of a contrasting color to its fur, wearing a shirt with bold, large text saying 'I Love AutoGen' for clear readability.\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mdalle\u001b[0m (to critic):\n", "\n", - "i generated an image with the prompt: Happy dog with a clear, cheerful expression, wearing a brightly colored shirt. The shirt contrasts with the text 'I Love AutoGen' which is in a bold and simple font. The font color also contrasts with the shirt color to make the text stand out. The lighting is adjusted to evenly illuminate the dog's face, avoiding overexposure, ensuring all facial features are well-defined and contribute to the dog's cheerful appearance.\n", + "I generated an image with the prompt: Joyful dog, contrasting coat color to its fur, shirt with bold, large text \"I Love AutoGen\" for clear readability.\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mcritic\u001b[0m (to dalle):\n", "\n", - "I cannot assist with this request.\n", + "CRITICS: the image effectively showcases a joyful dog with a contrasting shirt color, and the text 'I Love AutoGen' is large and bold, ensuring clear readability.\n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mdalle\u001b[0m (to critic):\n", - "\n", - "I apologize for any confusion. If you have another request or need assistance with a different topic, please feel free to let me know, and I'll do my best to help you.\n", + "PROMPT: TERMINATE\n", "\n", "--------------------------------------------------------------------------------\n" ] @@ -251,7 +252,7 @@ "dalle = image_generator_agent()\n", "critic = critic_agent()\n", "\n", - "img_prompt = \"a happy dog wearing a shirt saying 'I Love AutoGen', make sure the text is clear\"\n", + "img_prompt = \"A happy dog wearing a shirt saying 'I Love AutoGen'. Make sure the text is clear.\"\n", "# img_prompt = \"Ask me how I'm doing\"\n", "\n", "result = dalle.initiate_chat(critic, message=img_prompt)" @@ -267,25 +268,14 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "id": "dfa623e5-9444-4868-bdaf-ccae85d3ecd5", "metadata": {}, "outputs": [ { "data": { - "image/jpeg": "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", - "image/png": "iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAIAAAD2HxkiAAEAAElEQVR4AaT9Z5RtSXbfid177rnep8+Xz5uyXVVdXW3QDTTQ3WQDJOFIggTXzEgajIYjaemz1tJXfZE+aGktaXEkLXFIzpAcapHDIQAaDEmAAAl0ox3QXV1dXd48//Klz+u90e+/I865N/O9AsDRyZvnhNmxw+0dZseOiOTd9jyRSMztScz5w+L/+SQSyaTZMfAnu1480VfgwHgHDAptzwJiYYr8+D7NEUSGS2AL/4XJ3M2ql6WI6DDHkQriqY9PvvwifGfyuZzRCGYeJyZGGYV1WEDqikwJmM8VBw4uFDafLAvjUigvTEk854HhcAYXGM+otAWTEArDzMs8ZonkzEBnzkMQyUCokimBEAiXaTCbWjjeBBPILBGQmRmwpCAJFM6CJS2gmistPrHm7hLr4oxTNJ+5KM6WhXLjXaLMOasVjBAZBDFGjyLVE7noawkyV3OHGmOspBBUABlCD+NCRxgWjs6FN2mK4F3y5CM8KtM4XJRyUb9DokCxt6L21jOuAk1SHCTS17UQyxHcOAIs6wIlkfKbKTofxvnZe54MXSCC80dgoTGTIhJS93bQnrzMcfGyWtLLJciFWXhjirycY5w2Wc9BO7/I0duWHOMEKaxLrr5/hsdKMSoyBx9Fgw0jWJzDIrVLAOdiMB8FUoHp8R9LiorU2b3reQAXlxGjsQBcRJgZxEbFUlVgUSUmEuKZeTKYi4VS2KdWzOZhcSfhPXnPE9PEbJyYDObTXnLam89GycR4Pp8AnkxmEsl0EITzZCYZZOZBdpZKY0jI3fyJRbknJlf95IV0W8aMHMxXEAtOlbclUwallPQR2oG73FsGfDEoQ0LgH4FGRb7kbLF6oKWCA52Fc0EcCh9cyYyQnvvK3dWN0gegg3Wo3NtCAOMBYgQOZQxpWCx7MYTDpmJS8Ng5CmjZk6uLmQ+GhSMMGodxrqELGSVSIZUoPTGkMwuj/s+7G+zZl0OwHD5Kz1k4bE8B9Yl3Pg5EqCxuewkJLspKDOSdzqQ6BlaAP/VxyTWEDqtzINwCjxHesju+i1RYwfnyWSqmRXBDRbnDT8nZNDmFVWbJ2SQJt2CeTxL81DMJK/0bkCJoMVoa5uEXwopBOhUEcKfqbzZLTMfzST8xaiUm7fm0m5z2E0mQiHJJWBLIIAeGRECcwkNwtbypzDzMz5JZfvNkbppIESsc7rJK1I4GyK7SMJ8mpnTASooSI8SWSn1ld0kVhCs4VUzEfsqLwypomQ3GFaThN3TuZagdgEflwrg3CBz+Zcdz5uWy9uBy8s4edRxG6PjH2SeOjyt+gSjDeplRoGcflxzy7AAiT+dsNlcGwiAQQwgaGlm1rGB25YUxxOhiilNoWBVGoPKOH8yxi6umCJbaiaDiAA7aOceOEdQSJnNyAGewRBijsMTFX4TVjJFXhPV8kOXUW/AI8MzXQjm8UX7wp6336Pw3imwJRmh8QGeKrBY+KiwHYbDQa2I+Y7iYnPQS41ZyNk7OR8FsyOCGYaRFpEoKEkEyOZ1pxDOd0jlqDEqHR28WJoPcDBYSk07m4+F82pmPO4npIDnvJ4OJ6jcIk8lsMoDfAt70geCDG40WIbHRbD4OpsP5rBfQMSaz46CcTBWSsGkyhLeNS0gxbcQ0MR/NJ6PpeDAfDQgZpPOpbBFk6lQTIaVDAumIk4lhYjIKpsQOjpLSloDt6d3JkKMTsu0KU4UXlYe6BNKLVaQcFauRpoBxP/e4tgGcMQoBGJzH6bCfD+owKRw+lgK5yGZuPha5GceIKZUAQ+3Cms2jlReusYfMhtmAzJngNiB1wWyMAU5q1ifBYVA45X8eupAW1KNdIFR7u7DFSXOoowiWAECA7cnHHF0UwmfDLAclR3PhhZdPwcLP+zrEBigyw1VV5UvKQZ8JG1ek83MBXUQOFe4uLuclRzN5SAWTUTC+7rxBPnrwIb0+GQITCuzQleo5Ciy0mHEM4T1oFuqf9ODA5KQTzPvBfBKoaZxRGQAmIV//IYQmgDNx6XA2n0zGvem4T4dE4cEw8OV0MuKVCmZBwDsIU2m8giAP4zHahFcTDDtTcCDcSBLEhBaJ67iIZ0ITMJ/3U8nefARvZ1KZckDHNp/Op3161+m4PRu0p8POqN8cD7rMJzPFjUSpGCaHQXo7mcpNhyf0G6lMYTQ6HrePpqNRKlfOVHaSihNWLAepgjKvnKVwSpISysCVnZUWafLlZY5WUvJwhalillFcqlLkz0gEu4reAPm4wsfqHxfcIYzc+AqMt+CgHL2ci/mY0ejK+xpw/FKoCDpyjOzmpRf/DojaN+RyiMKJIAihFif6i6CZi/jhqAtgoRbRKVWLJ44Ep2VwlaNRozkqJoWJvj68xwQVyORt+sSY4mJ1jvjhRYLNoIYFuysmtZ8C8lgwxhgXxhhv5GT4ZVkGXmZ7hy4O5+NVWl2ROliXKLohzeLEXK5YFZguAlq3tkGFTXoJiBPt3Hw2TE2H03ErMTwKJvDelO4kpf6JHo8H3iNwCu4Sp7g41W7SqSTFYEE2SAwSdDXj7nQCntEY3qAvDQuF3Aq8FqZyqVTOpnlF2IM+KwETMvGjG1SqSG4KZIZRNqJgLCspz5QXXXEH+g5GJ4kkI0862H5i2JgPmrNRY9K/N2x/OOi2Z/PqJL89TI2SyVGmcC2ZIkO7KTrSbGHYfTQZHhNHMlMdHZfUC4YbifSVRFihFFKZaiq/EuZWg3QlSBVnQQbOVWOibOqhIlVa1oDJikNkdv6+pg2clwumEj73LHk86UneLdIojEpl6fEB7KOXZ1QP4VyWLEqmpXqBAhhzU25cHcZ+5u4idL2ah4mo2vWEMbglIoqMWnP5txTFMGcMFsBKMYrb0Z8DWkYnF5fQp/mRREuqPgoVvc2IzTWC3tkhOPv2UUUxLsfkAH1d+1A+srM4sLlwUWiXMVxJjhWiWjn6rhRd1GQ4H49NkmLjyVQizGbCkFkWRUrTlgYV0hKxwHQ0He6PBwewUJAYieFSmVSQTVlPpRmBxopwo0Qv5NzCMdOa0nEmGKrMpzM4NpXJpArzJL1fZzpsjnqN2XxElxcGs1SQghmSidwsmUsGBaZ59Dkzep5UJkgxA5TgRr2QK1LlEKkpBEnqQphzPh2kQ9I8Vgc4HyHdSYzoAzG05pOH89mD5PSj5PBgNgkH3VQqGGeyqdk4G2bmOUa/sMtonJ+PYTyV6DQ9m8Bgqdm8MJlXpglkP4VxsJ1MXw/yV1LZepirpkvbqfxGGBYpqNkcYCtXJc4ZSB8pFBnrK6OeM/yDPXI3zyWbuS97OrMhw7js44L6d1TRS6gjWLz+BPp/EmUUl0flcyWKtkdkKNQuRr2TUMxTmhQf3kVgKSBYjE3h4YqFHZP85RCVpMX3xCsKY0EX4Q3OkPrULXtFPZg16IJcikLgcnGfJcOScxyXQS1jjkLFX4dHID5SRaWZDVwH7zEt0zsxm44mjNP6zdmwD1/NZ/0gMU5lg3SxHGSzqZAeCSbMJhKM3JhYdaf0Kv296aBBj5dK5xkiwoRBmEuG+QTiFno/+itJTYjfKgWSY0wIVyhyek14ccDIdJ7KhunidNiajwfBlDlcNp0KrTtl8Al/wv8FusFkkFUfmMokQ4QuMAd4YXx6QmrSZCgJIU+CNZkZTzrpJC0FGUX2ExLtbEZcjGzp+noABElyVArTR/Nxczoew+8wYY5pzGwKLlfnlJdMLu0qPRWhCXJpVtKzWX7Sr437a5OgMklVR+mrYemFbO2ZsARz1hg/T2mz5kyYVPzuX20uFh4zebNRiHN+8g1M3GOc9RWVa5bAR4lckDtxLNGS6NvF7oIrN9GjYJF56atsRsHMsOR3xkgSFH4Ru8emRClWyIWknWto4hhjAyhkBthj19cQxGlVHXtf/10ObdBRYOFwnlGAhYsrFx9UJcdP9LkIi58lno+VFCCKO0IpVPGjUOpE+fow3suQxGAYFCMgQqQS5y0iU6c3DpiYzZjLjaazCT3gZHg6659OB63EDLlFY54cB5lMkEDeWE2kKtBxEBTonaCtGfwz6tGl0L2kNH6FDWG/fCosJhBOBkUkHOJAxC0ax+I9teWnWSIcKWZjfJLDIHE26c5YeKDVTBcMg8Q2iUQmmczNgpIGn0lYOjdnTkgrkGJEmINpNZyVmIRsWW5kIBSCHjpRPhNiDMI0MqH5LAxg9AA2DubBRFEk1ueIioKVZGojle7O0iNERQIczhC/imlSapVUurAQ0qMpo1v30IiowNWla8Y7Tqd6mdQ+4+35JD0ZbYw6r7eOb4WV57O1l1PlKwxWZ8n8XKNxMmoPQSkOV284EIOjF1W1A1jUtmrLql8VbIRAaB4PqJTw2BsnR5ry079z5Y1F9k96DMKBRSAOPEoPrmfCK9ZlBzP7pFi01iz4UF4wE2G2rw8vkllGJXNsVz6xuFwolI9RbpGrh3ZO5h6/ltPnChZgClDvGMgCGgUtSi72jMAEZFUQOcQQGDzfAWO+YI8TExsW8J7VaTZZ1kb0kUY0P+kk+seIH6eIUhitTfvJGTKV0/mgPep3SG+YTqWzuUwwyYRIHcaz8UAj0vEp1B1CVKPubMwwb0zE9FHzoAzDhGEpod5SzDMT80joHydHK+oqBnq6wSw1FFvSR07B1UkMkJQy1M1nC+vEPB414TSQpFLlWQp5TIEZVxBmk2lmg6mExq70xmIUy71risgc/KLYaB7m03YmXdBAlDVGIOcTuGgepMPC9mxUnfcOg2A4TawlEoMgnKTSk9no8aTf6yORzaSyWeQ+YnZKWbLeCc1EoHGzrPZJpKZThEYzkw2NU+kRwIzAg3CYTuzNBx9MBq+PWj8Oaq+kSxdytVuZ/I5akDnDZporNTBqgqzKQKpxkOXDHMR39uBqmYl5CxhRgzLosq2PoIy6cI1CuvBytQdSF5/rzwJHcFSMtY8uFoIvh7eoPCI+UVBnXHYXt2gFh0dpcTjUPTsgtcEyWjr9m1S7vOAh6nHAguDfp9GFtrfH9KRHDGzBlkJERrlHfj4R5kU8DmnkiWscy5NeETp9LaTyrJS7UIuPgjqUxLCE21DKTuMOdWtZbBiw4IZAsrc7HxxJmJlAijigUU/O+rNpZzbtzkanjEoT+VqhcCGTySZT0BlLfQgwB3Qfg25nOp2m03REkCcULollENYmcwaNJWZoWmmg+U+ySKCYXR+CibQbH5JAlgEQ3eDGzK0fJGHARnII/ikEnclriZABZzLMBOkCBo0/iQKzuD01hwnp7mAtSsFmheRYtU7nbhQ5hQMlyyFyRo7ZGfmaj5hj0qiwKsiQeZ4ZTcbDRHKYSK0lwsk82R6PT8ej/pyFSWamkwTNDnIlceBYPy1YInCdJybTgNUNpsP05BlaCbKRJvXTXJhIp2ku+mF2GGRGWVqt2WjWfHfY2B4dv5qtvpKtXkzn1xNhjXDEx9qM0RztsIpFFlej8LlVGe7u6+gFXx4rzohnzIWXhY4s+kYUtnBTEIfBvb2PNZBUhmPPCI+DVdnZ40O4Ao4c9RWcSE2SN7NGuPW10bEljdmIYzmFiZHJ4nnOfZToM5RreY1DuKxbKP+KYrPIlz0WYTzCGFJRRrEq01Ee7avseDRmd5YIJPICJnKK4WM8llPhULHoUetiwylNmGz6NQlnrIm1ZqOj6eCEWVBicqr1N0akiUkQoIDCkvo4IRf9GI7OEFcOB3RCdHyJaQ/54GyKqGLIEDORmHS702w6n85KdMIgVcKXWWE4mRWyRRiIOZiJZJSiuDhJj7X7NI70gBNlTANFGAwE9JIZW53oBLlSikBhlYllmCkGMCWiF1gHgQnrhEhEmOPZQrrLvuFXrkVUtCWjAT1wEnlMcoiAaTrsB6lRKqTNkBCVKBVZOhfma/SbwXw8GbenidosWZnMewzHp4w/CT/UFBPhFKw6HhIgGI4S/f58OJlmc4VOZ5qipThOZSSglTQqR4MTBjRM2ew0Xxxnip0wM0zni4Vcf9Q6aTdutzLb2crNXP1mpnI5zDJdzEw0K2YtUuTna82qWgo66hzMFReSHJGAMin4yG6evCIooxALSHABR48CGJ5zjhGiZWeFiRgyCn/m65DFQVzqoxBKC88CMUxoj1y8n1GEuufoMVwR5caIzRfbAg4T9rMA8nZAsbuzEty5xFYfnWO/GDpKxOLr8kJ6HGrhcYldpETAlmBDE1eIyh4XH6PmSSRPK3gJOGrYmI374/EhqwiJ8WmCYed8YIt4VDfL1hNGlXNkFfRECDsRzEz7qYCFgcJ0kkSCn57mgzBk9MVSoE27pplwFqQYz02m03w6rDJLm4Iwkx5PguFknsnl0OckjUqQT799Rdi4MCHEL63uDtmG1ilQtAmDXBnO05yNoV6KKWWRGSDdTZApwTnQOZ2hxrdo2BhesmoKZbKAFaLjPZnN+5NRMSzCXqftzrjbrhUzqXR5PhtMpgNHvqzcZ7IlusXpiJ4wl8xWg9xmqthLDofT/gH9/bAfUuj60eNNEixhkP1ef95Fwhqk24PUiCE0jRQS0lTQG8wqpdyEVY3RGGlSgSQkk73BKAyHxVIvzDbDXCGf3h0PN0YnH4662+nc9Wz1hVztRphfm6coBBuUq95dFbsi05vHnEiHt8jqbaROf1bELqDB8AIY1vYhIkf3xVWwoqkzYby7eRlkFNx5xMEstEOlt4rcSt2QysSfxjdqQaxL5wMTLuJyeEW9BuJQefQmRHcuCr0I5gJ5H4VddjDnRQQOincEhZd+lkycz0F6q49s8REa8+O1FJszWuE6/I4QBSJo+8jAPyVBQYRUxXSYHLcTw/0pSwijRjBtJqV9gkIZ1I9cAp6wB06cJCbjyXTUG4+Hzc4BHV6hVM7maxArQOauNXVmVGEmq/kVZR2kckFqPOsNhtkUksB5OEn205n17qCfyiJN0TwtzoElT0Io5+TSHjAWZT1kng5mLADSnzDLCkNmq8NhMmR4nEvmQlbnEkFeyBjfqhukTv2SI8gNnzjLcq2C746GEoSmMg8PH4bz4Up1ZTrtNU9OgkQ3mwmY4aa03A+P8aIjL6Wn5TkKA6gKTHpBvjnvdke9zqDDUiet0xy5MZ12bwimoDOYsJIynqSPDob1zepomK7UA8a0qVnQ7E96CIUmk1RyHDZJX1AtJ/P5abfB0HqYyXfyxZN08ShfvDefbU+6u53O7d7J5fzqK5nqTipXYyTPksaEblHF44RMqhcy5RsYq1NlT9wjZ/M0GE/MuCqwfwi2ROSRq309lEMMqugR6gixZmm4x5FHMPq68HgbhJKruLBaJZjZQMwqaClw8ydk8eM8Y+sSVtwA5Cf0TzwqAXue6huD4+szEwPLSTgdxcRIzqTKh7fmLcLl4luKzhutLgBS1l2CXQgYz5IuySdLdvNRMwnvDfcSw8MUCiLTwXyOhgr9HqMwVy4sCDIB0W8+ncwm48Gg02kf9noniTAsVHIplp4ld7H0o/4Clcy642EvxYoaapmIF1NM2mDSUXfUnyLunwbD2SBMZ9u9bqlUt/SREqVRSfcV5JJtWMVauCKuQAbCUJQZ5jQVrs5n3TAoSYjC8E4GuE4doIbVqnTJWsESJcwb+SB7GYym+Wz6weODUjaZy+Qe7z8K+sfl9KBSYuWEFLIeQ2eOUIdONZHL1abTEblSzubTcNRLdtvj0+GgNyQC0jYZa0w9ZrJMyTLVmwfdwTRdKPQY8CYSndY4Wy5Nx8PxYKQCTYWD4TCXhnVnx415qj0rZ4LCcJ4fMo+c0BVnZqepzEk2dS+dqo+am/3e26PjK2HlYqb6qbB0KU13zfRUTZGN1FwGhZeoXFWTRRloM/XlZ6WKwYxn2BAwsdISjEGfe6kIzz/OzcWpSovQG5zVoUOK3Rmga+/ia2IJIx6sE/p6jtwj3JHdYcKV35/+GNBTUCyFjPHEKY09VXTmjRdf94tYSW6+MH2WYkwegdl5OcQRRzsHFYNcbMUPYUJnOtid9XaTGnw2ktM2jTT0jaBdusqSk1A9SPkYhVI+Y8lX6A2mjFeHg2GX5YHkLDViDlRFpxrig2o1atVYkvUMSf+xDRGNIGZlTDZOZDKFwmSaaHU6GXrKRHLM8kB6DBMx4WNm5pubZYogWmXYVR/+0F2QgLUT6IKBeM4SIcPdJOsQWmaE8UyuiJ/yMEKMo4mdpDUwpOQlFAqOvf600582u916Idtrtx/c+TgYHK4WEfMmx71xcpxOMrLNFFDzVm+KmDUM84V1V4RIPTPjIOzNk9Uw0XnUbzA/TA4RThVT7fYwTKu1aQ2H1fXaYIhoqre6WR9Tfqx3ZJmjZkgGunHpCss9o06rzYiwkCap8/Zonusmy815sZLIFee5Yq9QG4b5QS7RnY4OppOP5r0Lg6O7yeqNkAFqeSfMrSDQmqjtonDU3BilqMrcv1U1lqhUBbDMcNBVTEiiMR/Mgdn7aS+gVCFnvJzNCM13jQ6Ji0Bvyt28XSSWVovOJ8AcTA1XVah5jPnqbUPWpdgsLvUuBrLIgEPhnAWDz7lULiE5ZxS9GYE5JLzdTx+LysGDVphdzBb9koOBLtnjyB1Nu4R5hKoTWu4pY8758IQBz6z/KDlGKaQjHZEJCwmsRY/HErWzVB5OEHJSfgjpkDxmkcr04EAYbNRrd9tdGCBfrWQyJch9wsIgQywGjikGjLAxgWA7ulP6RRYApJnSHSR3j5PV1cvZXLY37CPbQdwymbcKlVoijeDC9lVYolWE+FrSXd6tK5SD5nfSTIGFkVnQ9TBahv8R0ECOxshUN8LP6ZRRsbSvkywS0EHilSKRNC6sAx63Bifd04tr1f393QCZzGSQDSf5fGoy7jIPRXVtPkAVu5eeohNTTrKeydaNkAnsCgsPMEyYm4TFYTI76I2PW63BgIFtEE5paJjwpTMD2qgRUp/EeDKrb2/TmhVquW5/mEkzQq9kUql2B90GZEYslNI2jGmJuswMiRwBUTBt9/GZFUpBqTUtlpu5YjfM5zPpRnJ8OpsdTY/fHDQvBKVP5dY/my5tpkM0cnKUVqRzIyLxj5hTRk8fFJ2VpyvaCMj8DQxPs9hHrzMOFjRydD4xoDNEELI5hEbAVpVyOtv7RVHa1yFAdUgsqIjFtmaIqSCy6stj3kthnWv0VsRPTaMHWASMoLyLL7EID188IpjI1Tst5fc8hE+fApB9NT/iO1nJlwTlqfl4Njyad+5NOndmw9P5uDdH3wVxOxKXMRSWGGn2Nu22B1rERiqKCJGx3zyRS0MZbCQ4RBbY6zSandba2mohlcvnWZSHlHraGyR1Nt7pSXIyG3cQ5bNINpuxTpAbS/WsUswUdx/tFmsbKxubzU5zhoZXe5jMFIg3n2EFTYM6lyUSbzWiLMB4vthdAZANuE6CGjSkO9iYIwlGunKY1G5I6XvcV1cMu0KjrKFLKTsJydNTn5wclcrpR4/uV3O548OPVvLTar4UjA9mSTosqciN6bnm7LFg0WIS5lCRKaN6Jj2ZTJkOLEB7JrfBqDCoDienH/Xax7Rd+SkSqdRgMuIX5BDVlstFikeqONPJGB5mCID+TqfbyeRKYSHd6x4jzu03hyigpvJlZpt0qWi+DceTHOnszfuDeUcd4ziXH2fy3Uy2EeYepbL51HQtMTka9T4al1/MrHwmKF9OhCWyr5Gw9Tcid0dOcVGKAPyDmye5yOVpXxfS+XhkTwP7E91IjiNhxSda8vSoQE9JBZW3SBn+xkiOGxWCx6VbSVMVxw7yih8LGNsWCBdOT5jARLk55O69AFFkSw/Wc/E6AFfuli2glwcfyoVSS52IN0JELEj0Bo/nrY/nw6PZ4GjKkJLh4hh5w2TMsHKUYpw2mASTERv0gm63Wyjl5IqAbzQ7bXRqk/Sg1chkg36Pji6dTJczWeYnTBJRfR6E9IL0e7P5YNAaT3rjSXc0HjCFms1ys3lmMA673WO+9Y3X9g8fPN59UKqtBmERWSIrDflCeV7Mp1n0Z32BPNArUvWQFYvpbHRA2snQU5lQgWhkqekDBUJsY6adDGZpXiBE/BWIQaiyRIejTUx0wlMWGGCGGbNB7Vxst1r3Hhxs1QqD472tajoXDMlioVCjw86xUypFL4pIyV4B2xRnDFOZjbI3Kp3J53LlwWiUR428nq1fCvqjoNF4o9vsTJhGZhLZIDULc6ViZZLI5vNlKgQZLoP11GSClHUyH+aKdRAFiTSysFyhGK5VSCfSLybiTMWR2o5YmBwnwyH1ler05s3GNJNJ5ArTYp7q6LEokyk00vmj1LwyHv6403gntfLZ3Oqnw9JFJFIsTFLhRu8ieRk96TpaMW/KSDbFp9L0ADI7oCiI+dmLoo46NAflAy4gzPRkQA/gPBaBnGlhd2BUIQ+J0lRW+XC17XEsLOZJGSrh53A4ryjEn+lLEPdT3E+GUGpccUXFw1cJdf/iLYWTTY8SEJlllxkU1AM/CHCQGByO2/foA2fDkxlSOolYtBI97g8H3Wmfda9ZOJiEu0fddqtfLhUSQaFxcIq8cTIcsLJFP9p+sDdo7dPC7x+dpDOZ2ioxspnhFLmi1vcTeda/WOweDhqDUXeKOptGXYhS6ISGQaJAQrqNx/3xd1bXPvPBhwfHjw4Lq1skkOEoU1F8c7m01FBCugYmAyxzM3xEssGmIZYoy8zuyJRbZrf8UTwADMkea+qWX7o6Mk1J0OdPGWQydw0z7FcImB0ylxvDWIAE4f7jgyAc9DvTy2vFUq4XJjKVQg7hLnsjgkQvTObSKCswLJAEn36MLrkvjR4WOpIwYbU0T03nOVYIc/W1ys71k+PD08MPKNSwNGPdBeajhaJJGjHhzWVROdf0OqQoWOiELRk50hQkCsU1RDWM8vGYjFm7r6CVTl2oItlqOU0wOmUyOWD78igR9qbt1KxcSJbK41xlkiv3c+VmOt9MJg76Bw9m3Y8z6NzUP53Kb7Hs4biLRINJVLHMQUYkONqjwvJG+zjLGScrVoNytIXnWf/l8OeROnqM4M/E5rCdCayeEGf5GBcuw0c4XAyO9M8Eji1APgV37I0h9o4Ny75mXo7OQXkX97E4cLevnNQaqJgdvy6jw5e5CrqNsENjcvrhrHUngRR0Sv9gHQNyizFrfZNuZ8aa8niaHSWyJ63WcJBGwn7U6V+6cqPzeJjLhMXSzv7D94sZuJFYSoenbZSiL+xsr6ysTyeDTvMok0nmSywrsxoI3bfpA3W0BAvnYS6braQyMCfqY+nGyWj8aNI4vs+62fbms+++87DR7qSzMPMa06VhqOV1zqFgVAsiFtwTqKqOTunJUimGnXmW+U0SSVcH3cIf6KeohZlOByaD1YyCHYtGc+wtxDTv0xmy9SK9gmgTNkckw/Z9uqad7dV8hkNokIWSqzbabY0Oe0EYlDL3LXdH03RqjMCV0qQ3hDOZeTLKRoAUaEd/JpcpDgvJ3DBRmGVKo2TtSr/ZGB/f2+t3+plRMBhPe5PT6ii5nq1ILV1bkBEmaZbM1BoOBBFdd5r1wmDIhLuQK3ZmxzQZmdLqJJ2fj7q0LCgDMPTv0XiwTsrsMaAZCPv9ZLsf5Nsz5ufZXKe0MipvDIolZu7N8f6dce8gs/GlsMRWRiTSDL8hEzpF1yU6glGbLKLRs0yCkZvzMaJ6gp7Ow0SwfPEybI4uDbfsIk2Sb/6CefoTe9guChetki6c7iM8y4+jdbl53Muef6r5TE8VQTtE7n02sgjCvrGXJQwn/9VHLUPs7/tDOhLoKzHpT7pHw8ZHic7HydGBOha6DiZMowDtx3abGUx6LNl4YjgJH+41wlItXSzSe11c22YgWlq5lJh3Wq0ThBmnrUEpX3/w8HBv9/jlT+/kyxVmR8hg4MBCYSWbybFdfTLpjycDdtrSgaDFBgdmCyupbCGZRJgZpjcHk0m7dbd3tPfx6k754qXV/ZPR+oVLzL6m7BuaoG6aoB+kb9WgFk6btpOTFhMtkRJLHcqopGVwoPpDnYLBWJT8s3KGllwWNwmAmJvaLuFsOkDVdTxqw9gwAZ0rOjuolMGqF9aKrdZxoZiHobs9RtcdRoyVcmGWLTxCYjnorVfzs3kHXqFHlV4CCzaS3oXz9BRRaTqdKeYSw3wwHLLAPirXL69cggHnR3cfsGYzn7HJMDE97qRSp7XkKqSUyaBDR+co0SglA1Ip5aAmF+YZepKdSm1n2GtNJ/1sMT3L5CbDLhN1htQ0exNUcSWy1uKMNlU25hzvUcpmqxX2RY/GvePqTj9bbmTCxoStV+Pj9PrncysvMc6H34hNZOFpw6QdFJecPunBTwXqyV9Q3uWTAjj3p2ONsSjbmhfp3yMSm6n30GjPp8gm9BHEEqtEQRZJiNPkvZ6EcLBPd49DRwiVgiXHOEGRv/86kKfjBIRg8YOZ7iCBxGMw7h93T24n2w9To4fB7ACGZAUCDhmwV26c6fYzzXa6PwzZLCQJW5grr9fTldVSfZ0BGxQQjrtD+h6Gmohm0ANpdxqNg1li/MJLt27eulgsMwrsM4TNF1aZ/CAa1fJh72g04sAIpPHFLGdAJDPD3nTSRTWE2WKGodn6Wq7Znp3ceXjw8N3tK1+4mF9b2Vwfjaf02LCitGtoOudjqA01cRRTwwQ6KbnkrIUidDKBJITtRcqhCCrZZ62BgS5mlv1IgA3fIXDUTSnWUTZM5EIouEtnxtgZaT7jNBuTBp328WjY73c6xzNG493Tk9Pj/dMHe/vV1VqlUr9xebs7mFeyydUKkhh6L00ztfSiCS5LgKQunWNymA+TvWEqX8wwCiiU4c5EOtvrsz0RqWaK3uv46KTZauaL+WKlmskV0upb0VZTUC21kEhwp1li1XE4ucLaCA0kDvsIUBPnYA719WQwwexQa5NJxEzDLroBiX6WRCOAlhAJkexk3i9WRrn6jNaTHSujx+zvmObXP5UKNYAn4SY49bSz+Ch6Peqs9PZEdJbGFraFyQWL3hEa46enOJIFBsgRcmPFCIqvx4o/1clkXF4Rx6oPXcSKKY5qCYEzugw84fwUh0/G8RTgZScXME6Fs3pHfaJC1JBUUotwPgnHnWHrQe/kvUT/Xnp6EkylAmoLEMFoFHT6xf6w1J2EwynTjeS8UM9XdwajWSlTYmLYn6ZYdSb/5XItzCDc67Ostfdot9PvD3rt9fXKrRevbF2oh8EYLZVMOlWurDBvRH+m1z8YDppQVybNnCrLYAtByHAUdPucoVT+wdv3OAjt88/ulEpbifH+/uMHQVhY2fpUpxEUK9sMHfMgDNi0MUAOJC0zejCWLlNhUlvp0wlUyVNdzmuyutGYMzEfzCfdhPZUzDmfxtYtGNEQlGNmUixEpBKTUnoy7B2PBt0kGxeQhczTmgNPZv1u8+DxYaPVRBby6PbDb/z+tx/ee9Ad9NgaXC5XV1dXX3zp1l/7xT//qQvpamlYKDMY1AwLkRNlbIehwD3s589XRquIlZKpEvLSICzfy7+1f+d2v3/KCDZVSmcLGcSznUancSKJaLZQZFTLTrBSpVytlMsl+jOKFpEP2gXSD83kaxNUi4Ys1dCO0XmisqMhMY0bkiJRZMiyB6NUtlJy1EbQ7CUq7aByGhaLk/L6QXUTKSlKv7Re49msV9z4LONwgpF4t4BhJG3dj5EX1HKOqMw5fjlPCxS7nTM4FAv2iIEVVtSo2FVmjp34ukdfH9Y70FUyUpczDax9nYe9hW3Jetay7HHOfCaKyG/ZMUqP8Dv35bcLsQyPi086pqhpwQ2jWgxAmbUwYuH8iP7jXvPuuPF+OHwczhuJ0cls2BsNWXjI9gbJzjBkXZgKniFiz2ZDBBLFtXm2mi7mxDxI06ejSqHAYjxox4MmQp1mu3PaaJyeHNerme3tbVYmkL9wPhpkWciX0dmeTdpjUzrVbJIZTJCfoIczYqo5f/xg+J0fvfPh4z/68N7xQat1/erlGzuVen7e63dbnbcud/qrm1eg5iDNVJCFPBbKkUeQFeQS7STnprHhMJFLzIuzRD+ZaidDtkeQY9spb+dcaAg6YzmSbjlDBxUkmA5JBELBIDTKpkb1dLfZPhyRryTylMK4P2m1h63Dg73dB7uPj99648233/vg9KQ1lTK3Rp3dVqN5fHS4d3j//v5/9is/9/XP77DgkmcgqZOpJAmWkImSZuKVQemsPmU3FoPUeYrlwYNHu2H+4WQQwM7zxBBFPq3fZPJsLh6N0A1oTmapfn80vr2XLxY31lfXNtZW6+VCie1XyL3IP20XDBjORshLxwmktohltfWE7rGHth8srYVKNl2xkDmZ0kYNuvNBOazVOWMHGfJRbkCDkEZM20fXZ54rb30mlSqyH8wISaTi6Ors2zwXTo4wcRQXxVTqQsbWBfgZUxzCcaCLz3jK0Cm4ofBw7iPU+qchtoqzt4cw7B6BmZ2DCxA5WICzTs7L4oqglkP6EAuv5eicq1LkHnXNkTn+WlkafuFSIwO96ZAGNv71Bq0709Zbs97HqXEjNe0lRs0JK33DZKub7o3CLhpRabbzBYNJslBb1VF/iRwHLqRylST7bebzLOsPY+0uTyJObbd6rVa/d/r4zscHu49Yw9jZWS2V0sNhA4k68sOVaiWHTJGTJiZN+iW0Xhio0RHS5fZ648Oj4AevH/2r37nzw4ftBupbNIqp4MHu3rcZJxVz67X8tQuslfVujoeZXL62PgmnuXJhPhkfkxsWDOgA0XVj6RBx62TW1aiGqWpmhIRRHR3DNWSXDNHQw2G0RnfCPqYkwzxGrdoSrOafkeq0m0kMM+ODXutwkGDEttbuTnq91NHjB/t3H7zxxvsf3H3QbjNKH7JyQWky3GSc1++1GQF+9OH8H/3GOFf4hZ957XqYGrCtGcYjt4ha6f2s3ZsyWM0gJg5zuUJlbfvazVfFIw/efqOPnKcHz7Bsj1R1Nhom291hs90fDdEapRw49LTbavaOTtobm3BifWWlkmWdlF5Dp32gW5tlEI9ODYKtJJsktVGrNRl0xwMJtJGsqmejADgagM0s8zkaPNPj9HAwXd9qZZN3k8iKp5Ve4i0OpSquvJAK2K1CAJsk6ksDZV0TM1ORUTxPE4UZuZmz3LHxM1qLyc8MztX78TmLxbA7VAa9QGAoxVSRE0ZH4mjiRSHkHflbeL0sktj2NO/Y7SyoDyNH8/BgfJ6Aw8E5x6gU2MAWsM7qgztAjc44MiycDafdg+Hph/POh8nJg+SkiTojSv7saO/1s91hutNPI/UYBQU0+OfpYra4wY6+IF8MU+XJPIW4j31rKLvAr0yqTo9PpqN+ilFb+/jkYHf3/u3T40P2CaUyE1abWQSHXgqFcqmSD5kvac+Bus1Qy9xStaFTOT2ef/vbu//gX9z+6HDIGE9SBcZbdA9BwDrBaRcqHR4ds5cCirwHVU163Wl9NbvKCschdAb90akyTOWYDHJH1emnTY5DWnwGabCDZkp0uNJZZUsf29VFnPMkG6mYN2r7BZuqdBQiBwHPe8neybj/aBhs9AaZ01ZwuH9w98799hDh0cq4v59KM28rbl68tXlx5+3Xv3968MFgSCsy/ng6/Y1/+61Llzdf2maTFNPOvpYBkbyGaPDBh7DMnD1IQxRhR5N0rri2dbV/C6XaZq/94XAw7nQQZrYbrSHLP9PZjIEnS/d5FuwzCDxZXJ2j0t5oNPZ385evXt2+sMHsMpdl9yMHf+RBro3/DFnocXNEWxiHncS8NUFvBkZnlkqFcdjOODEYMNBJ1Srsb2ZvFAKxTnL6ODmvc8hAZ/+DMLOaKe+gLke1MkJwxK12yhGco7EFzcWusZM3xHaRpT0LsjTK1TQdIN74LqDhRzGeY2v7uqGxXGhsCSN/DUGlaRE/ajKeZJLY+6zBpyRO+1lfb4t7M5+2peQvwzskC1TnwCJvik+tmXEoiU/PJ6npYND+qHvwo+ToMMX0b9JkZQ91KrqrRCLX6s16qKPN0+MkW9aQDFSypcuJdIluMJ1bHY8HnPiCHH/YbSFMGCKuaB2Nezq+GlHq0eHuw9tv7+3dL9azL7yw/eKz6+USxDQqlvK1Wj0rSQPLdKy8pdCuZA1ugvxgODttdT6+N/zx++1HTRyoBkSLJJdtQfkwlWFqiY440/BGf/jm+yeMu8ZIFk8OL+xspXqFbKAegIMyUsWVRDHHrh9OkEkkK7Z2hsxRO4aIDSakL1AFMvqc0o/RkBKGQ9IQk4bqFaUBCzPjj0QVZZZ5OG30Os1RJ99t5U4PSWN/fef527f/zdVnXjs5PgoyYWVj/ef+F7/8V37t1/7O/+3vfPSDf0nw0+ODj96+/eMf3blcvVkr0jcz5OtxjpU0BKRPxDAApkpXyiVp04515iJLlxNEzsPpyeGwxXZo9PmMnigjUkpbFNKjazmTrpcx8mjSZ7myBzd2et3VFZKwlqNL1KIkp9pkpMoLhTLOTqOQUObEELr8VMDuzSFB0a8Zs/SiM5OZK04LheRg0l/rTNd3OqnEXjJRnaUed5uMkFGK0E4o8aCRfER1UJEnJFw8cUZ+S98FSRrUWbpcsqk/UCdrpGl1I0+Rqx7eLgpnlVkw0aOekAfSdvTtoKIwEZT/xrj+BPcF6nNAzoq3pcyDqSU4k74YCkOUFnPzFoXTSELDjnFq1BycftQ/fjMYHyanDQ5rQL+63xn1urS1CbQnB2NIV1pj41mWE76C/PoMJawgly6sSI8q0MrxoMmotUWOO62T04N7qG3kCoV+WwPRh3feazdPLl0to3o9HDWS7VG1kC0hy8mzqM7oDAk7y/kSp2s6OU51WqPj/dmPPhj/wet7nRHzESZTMwZZPPX62uVrN37iJ37yn/zjfwhjQ02Nwey9Ox1mq6Pj43Hjzvy4dOFitVItZ9PldIaFOR1wyLI48z36UGZENs8XUSOvYEAqE6SNrgrjLcwkZialHYhbZ1WwrgJTsFoZpnP5LIJ+JKW9RDuP1IMcJtLdg8ONzY2VrSuswcyno/1HB3/4zR//H/8P/9v//H/3v/9b/6fbx3tvEMHtjz741nfeefnZrdwFdjTBeswb52iqq69l6Cup6RwxaamQR0LEuijCKoa01bU33vnjH0wfHBM9yz+cGawJNIyFGh3J1KIj42f+NaZODMeNo1MkRp3Gyqg/WttYyRfSzM8lIoYu2DuiD6MJZozsWeGcq8aw3UhwUOpsiB4RSy4Mpbttqbv20VwazdK5fjXVDlIns1R90NwrrdwI2XXJGFYMLUozwuNl9GSWmNJEVaK1mO5ig1yX3J3V4SKXEcrI2aJyvqojRUvO9XZ/mF0YBXDMGA1H5eXRGNYYpTPIj1Q63Of8sLqgTwv4JCx4PvEBT9R5OijPpIZfWcHO1CJk/aq329z/41njvYCNtgm2RDQ0bRhnOBDMduukoYC+Fnw54jM3SyPBq85Z+ObQB2QYaUZ6yR6a1IMODWnj6B7E2zp+jCZkLpc/Prx/vPvg8aO7J4fNlZX8lYv1aildyIWYV8sVZHvIMOmDmL2x5sjp1KzHoXAyHI07vdmjvcm//f17BwMWHlQqm9tXOVn06Gjv5ORgNJt8/Zf+wi/+4l/9b/6b/4dmYGiQdZNHh9Nbm+gnn4xYqMut5lndIAU5TkNkhZApUnnOqpct01ltQ5AsZEj4rjGbeIJFPnZRpCZkCnEu4y5JFekGEUAhMcmk5oM82xcCrVVyRMw4mXr5U7funD786LtvMr/tj5Iw4ajfSrc7ux9f/q//1t/rd6e5IoWDohxNzPR3f/t3CqnBf/Yrn3/uRgkZEvtrkymtvpD9GRvBGD0mMuVSEbW4fmpYWqlfeuZ5DvYedPeyYb/V6Lfas9POvMteaDahmP4q40LYZpaa6BQAtkXCh9PZsI1Q9VT9+mxSr1UKxSmlQJeojZFkUw0QiWEgW2ILlNSTEkcMMZJ9Wj6jbgQCgxl6Fke0w3e703Gzluins2hGtcaDVsh5GfGjhBj5GSEbpRmPRCQZkbEIzUjdOfjwzilGZgYxlns8vXqbYBUY9M7D3jCffXktMMNWmszoUWJ8Cs1+/mWBHOh5r/8ou3GST0IU8xkEaumXUhiZ1QKQRpLL+WX9xv3hwbdn/Tc5zn3GKnm/N5PyPnvVZl0kAZ1El+06NMFoRpVq88zaVKeAIlrPMNFidw8tc6/b6DQPO8hNjg8PH7+PznOveQwZMOl5cO9O42SPvq+2mn7p01c+9amdYmleyA7LubCQJw0csqKzaGBC1NQkrUiy5NhvNbudbvb197sPDukc6QKnzzz/6eeeeeG9d946pOOYjUv5yre++d3BaVOnPai8k/ls6sJ6jk5s68L61lXkE7k8i+jFcliooZuaSG/M81vTVHk6Z8yM/MwELjQplJY6oRTzRcaIXCPB2Yc65pDdEpwQw/CL82U43VSDRk7B2ArTB6XMAYmczloaPU+L1UKqWi89uLs3me0PuyfIXMf9w3e/+Zv3373wpa///Mnhg1Kpwvpmp707zc7ff9D4O3//977+pe0v/9SLF65Uma8i+GR1hNGuTgdH6ZtThzOZEXsIWVnM5LP5wtr2SnKy0ni8X8iOmNO1uvOODR2ZEiP1sg6R/pxFHqnKMjTV2HoCyzQPkCsPVsrwYaGYQ+MoBIJhLAVNXESoUktzghyDoGA+SHQTyRFlQTmw94XR6wjVX90L0EzmOrXaJDnqD7rH2eoVSka8AAMyhxFviNb9I0NsiRzPfhfE6NzP2+Uao3CGqKdTtPImSAwhO4/s0DQfkrQ8JzTfxWsRW8QJCz9nAgVeMdxT4jofYgGMTxwwTiEuKij/qLywEYUiYnQC8Y17/aOPJgd/FIx+HCZ6HNI+7LCJNjuZ5Npsm+2nu4Og3R93ul3WqnLF4mCSZj8ck3vWhqEYdvmo/+p3O42DTuN4f//h/p33j/fvQAXDHnjGDGJZyGbhN5cO8qxbULnDxiiYwCvpoBJqGDaE71h5g2MRT6Ct0us1mNn02okPPu5/5weNnlbOZwjob16/eefOR7t7jyA8GvlHj2+fNA66rTZWeKXABoFR99JO4aVnqs+9tBZmRqVyKVOop8s7ydzaPL0xCS9MgwobEcesBc4SyCG1fM5ZNxqfaTIqwShmdYgUG9IHrbFhRh+MPYdMJ7PBCqo6QdgMMh8VEunJ5PE0NZm1p9XCeHN7+/Dgcb/fRQMGKSvZYC8IspM7P/7xuHfy7Cs/sb93gHjy5nOfqq1sbm3X5+n8/bunmezhdmFTk1L+OA1LHTrapchPQIOGyzBfqm5sX50Nj4NRI59slfKTCp1ha3Z8mmj34BAYlxRrG6S9kpyWjNwEJIzuGYGyVbN1ejgadfqlYqGACn2FYkyTE4TDnC/AUgsDUADTnNGDSh59aWscMiWdBTppCjRaSGx1Zyeng/TpUam4PkIXB82+kFbMUZtKSuTlKI/RiNkieou/ztu9He3FXksGjwuXmGIxR4xnrsYcFgmmM5EZmI/BzQmXMBu+yNO5n7UtwS64yDkup2UZ7LwZuLM4l2weh9iPYGI/359T/MwEBicfjw6+nei9HTJHHw1GPUQBhf4gGA5YikgyIBywKWI6yhZLnNuVzNKrbKZzK/QVqFxpgwAXJgwHg/6QZvdg9957P3794N4dVgsGHOQ3Yf+pUgpF5nPJ1Z3MxQv1lXqWPeyQRzbDZEV0TvMvJmDXAKepMeTpnPTZ5NtNPXiU+J1vnL6/29XgcRagyry/++D9d99kfAgYMzS0Q9pNFiG03YE30yOEG5cv1S5eZl9islyrZYu1bGUrKF2fhtvToD6eF1Gn0bwTpa8co+iwiR6a5EYzZkioOQ8GNDoDZIzNdhsRTKlc20DCs7FZKWRE7JIvMSWlp+IEGrJfLEyz0/kBvcGXXr3a693vnGzevfNgwBIgGzVIZTbb2t9rPLpHL0Tp1eqD/MZKoVD56lc/93NfeTmfHDUef3y0t5stP17bqdoiOytDGTQc0NClx9LMk1njeJIvrxRra7WVKjsSs0Enm55lM3PkokcNuFEbB2kixMTQ5GTC6f65PENfukQEyezoxxEBU2+E9gKCT2av84pWdFG8gwqYWTAGZhxC8aUyaamnztKp9jhE90JHcVGDLF10Ws39hx9nS5WgdiHN5qrJMMMhAZ5PoCmRlScp2CL2kenpzxJxPh3Ak6zz9KiXIQ3BkvsZeAOMesJPjsq4eRkp5iVo0dSZpuAMaBzhUggBWP+m7zIAjtbtmZvDqzGEMwUcOXR6e7D/7UTnR6nxKQtPPYTw4/x4UjhqdLudwZgGccKpRHn2vmXza2G2HhTWs6VN+iuGMizETwbwX4+1cgh37+Hd/Uf3P3j77dPjFj2bNhoEyTwHKXHAUYhmI+c+JOsr6XwBycqcVpnZGscUSRszkchnS9qFyH6lXoORbbsxfetHB999Y/DGR9CItXeWq456VPYQMBDiq8YE4lEGbVaHAtbNndzVaysrG2z/SbG1J8eyRPHqPNycp2qTRGGiWRCKXulef/Tux3ff/fijhw9326fsQgaN2Ia2ZoBcZyiFVToXlkmy5eLmpRufevmzn3rh5oUVOjaW8jVeTqSqifKtEPW86RucTPP8dY5RK19/ZnP3YePug0dHJ5wul8zTbOUyxydNrRPMhleuXNra2fipn/zCy89vb21WyXupmju6lz1+/CBbWC2v7LCtmOPuoR6b8SXopdNDtLRGFD794ahSTfQKyXGTnltzeFLB+HmcbHLPBWw2Zo0RVVOYiyksQh0KjWZJY2X4MEzOmHQw3jcxKsPMAuXAMJsGkBEIPSnzQfx19HCmyLIJ+zx0HB6yKCmdM7qfTRrNk0e3aQ8KFz/DgEhkZk260Z2IC6JSikTGRlx6f+ITQxgJO5uAGVd/UrAYKDaIR2Jo77pgiYgJozQ4lou6bcUV+Sy+C9TmFiNfQDxpWgqzZPTYhcFhUbFAZM6BNwmRLc8AsbM73PvW9OSN1KxNd9ZtoQJZRuTX4/i+cXqMRgVp1bJZhbWvJHsXghxUSdPIrGMyHY4GHesDB42T4wf37n78/rsHe3unxxwPqmsc8qlkIZ1gaJbLJsrFcGs7d+3qxtpavlRMikNosLXAhUIMQnYWANgeO+DsXUQLnfb4rdfvv/c29V6qVvPD1KTZ6SA55yCaSqmMGJA9r5V8rpjPM1piQAV51cvpjVrm0lbuUzcrFzbVZ6MskspVM8WrydT6PFXhSglO706zeW8ye/udD//4e3/84QcftBlmTZhQsXEpw5oERUMHy4YgeJr7KNhrwB5iBs6nJ+9+8N7t79549vNf/NJPvHxtrcRYjhF4lqXMROUaO/lmBx9XwtEL2cqFeqn1/LTVv3Ha7I06yJWZmqTRZWdMXqzWLl+9cvnS2noV5QIGzxOynsrnVi/d2H8wYhTPQmYqU5mgLK67RzXLY2KazuRS6QF7/nLFjUn96nx0wFn6iCwRIKPWzj5d9kb3WTBhck5Bwun0eoxoSZ0fStMZsrMe9Xd2ObOIxA6SIXsiRZBg0GAbvtHkjzMQRF9T1EUz00RlmsxMk90JLU5Sy0Kc+sYFBL1EeWv7xa3rr3IeJC0gDZ9aPwXjJY40CpfZDRPNy3s7GFmiJ/Y1BwtlQJF/9PUDT1kNKAqn+CIYiz4ambokLQ1Ho8ZC0OK8KKUqBZdSj2cJUO2+Zcl7LT4OaMH9+PjEL2CWEuYcnYP6CvvDUQZGHewDbO3+ce/gjeTwdDjmuDCauywaW/1JAhEcys8aG1IvnBXBjm52W+ugTpZ9A/YbDYcdFPYZpfY6nPDS/eDHP779/of7+3t9DjyacWVEspxKVBm3FpPMgsqFsFpPb17Mb25nauWA/fC5PCIEmu0MVc9qG6qcQZ7RIFOxabfXf/eNj4fN+YuvXHlt47nvvzP44TuPj1qlVk+nQQ1aR1cvVLe26hdWy+wEQvRQLed2NiqrdU4BHJcrmZVSplhAxjBC/TJXuZrMbc91phhnabCqXd5vdv/97//uD7/97dZphwUxcgepa40w5IYmToBhPEum0OHmiKQMemw6cTTNrDnLPrwP3v7w7t0Hb338hV/+uZ9+loMg4FCILltJVm+Fw/68tVugw+fY4s7paqJ0dbNGJaJOBI8jVskXq4g9JSTKInAVFyDr0RQUAU7QT6eLw9G03WzChulMeUSh0DKQOuSkrP/ny8PhKF9mvojeTyc563IgKZrZqJuNColqKclaAhpEDGJ15jF8RZ6Y0iL/VP7oSNkylcjnAtS8WdOnp0RvCH10+Cpfv1rfuZkqFIfd03H3FE18hOHjSV9D/wkrOSmUgNOFSjqfLaKKvnV97dKt6sZlmmP40npZ0bORvEjbWILuUCRO3vkzF0eET7wNygdaeC6IN/IXDsMeA1mUn9zhehR+nTAK7BOltSgD8OPMs5hdFOCXwX/iaL1n7LzcC+O3lEyfgkXIyMHjde0VQeY0oJ3jHzYffW/cYkdSZzJiHwvXjMw7veFwXOE0TnZks2TAYEW3cGWq6SzbHNQ0j/tsEeyMRmwmmLZarcPHuyeHxx+/9aPjo+aQCRMqFkgwcsmNenp1dV6oBuVimm6QBehSLUs3VixxvAploRMu2NnQa3U1y89m2F3O1tNBv/Xg4wMUMp957Ur1wkV6tOc+9cLPd3J73cHB/iGin3q9yglIueQkh1ZOCbEnMyB2MrRGo2av1SmUWG6E8AastLPpKZ3fTKZR5UGxLjtNFe4+3v+tf/kbLLjp6ic0c0wYn2JlBV2udDlAREjPj1Imrb9dECG2RP6ZLI5TOXgJmqbl+aPf/9bpaf8//xt/7rkLGfUi80wyt5qsPoOidGLYgrxLJU7nZcEAnmezB/1YhQMP2QVPh08nzzCSzGsRgckbTCg+5shTdks0Z+1mejgvVNBpyWkUyYyX9LB6xGUx6dI02eaEi2zpwrR3cdY7ng24jYN1iQQH0rQ7U6bu/RalN15fY5gPo09ZNcxyGmlqnmE8kmd/cyKDei96B+LAAWsb+fUXn/mp//TKi1/MZAsMJ9gSKqGYaIx2mHzZScUsVOoKKqYVjDZodaQXoEtr1KQbmRqjxfRopA7x2XepIzEAI8GIWI11red0BBxRbIRBdpklD3MBHcQSY0ek7SH5nPV8cjga9dsGGrGnS6zDHrtF1nPf5ZQ8zetP8CdxyrNBaNBu2iLT9kHj0Rvto7sjbswb91nmQxu41UqetLULiN20yB8yuQo3FqGwj8oIuiYJ7o6lu0R5tN/l7KFuv7t75/bu3duNZmvcazPlG3HhCmvNYaJaSNbKs5VaUFtLcYxooZjOs+GmyIFMWXpX+tcwTwPNbBB9FG5TmGgOxpE0bAZqtBHNbl7b2HnmhUxhhaTSRW1las+qTy7PZhARG9tgViTpzAUR37UZEiNFRwWEASq737npARlQmC1mK1eD/MVpUvdXT8L8x/cf//N/9j98+KMf0YtAvaxAMoti4BdC6VDmnHUUbpuhC2RmOoR2aXzEk6nSDB6rrYFyNJCydmraefOHP/j7qcH/5n/588+sSJbDsd+pyoXUuDU7vcPVGpl8QZoGTKsQJgUc/cZS+hj9GWmZsOaThDEk06Cg6DQYTUynnKGa6A84grvPxYij+Um5vs4oHjmQ7f5Anwed7VK6X5n0TtFxSacr6QxbKsIszcVwVi3MB1UOUU32OTB/ljhpMHYNLpVQn5+Xy/mMOnKEwKgQsrGYWQRC7D5j48zm1Suvfu3i859N5usjZVNNA9IkdWRGJ9A+3Sh7SfhZmyESwpEHrhC1q6czoooGZrK6x0y8nkKTYIcalz2iYHJc4qII1/mvmNIF8ekQNiXBHB1i5++ZEEsUHYCKZMlF2F16fNTn/JZjNywRKhdd5B27OofzSBQpqfDO6sSV9uS4095/q7H7fvf4eNjvIQ/hwDTWISaJElP2gRpK5jIcgKfmG2JgqQG1YSZMTMZYB+62G6eNE+aB+4/utU+b7KhAPE49qp3XCUuJioZJQbmA9DAsl9gil+doMfouGx2lpRyDFj83T6RGDJCYmSJeZRjEKaLtQ0aSGjWNBo/T2VEqvYoSSDDvMAlJpMochi0BKIKC7CqTUtoBdh2iCsM6G4QFF5EPpnQ0GdnK9ezKp+fhKlJPOoV7+/SB/+NHb71J+97ttbkOlI2QULmOL2WtHFUwskXPjibKeIggMYmmF+cTcj5NOjcn6nIlt1YOJtPOYat/uJcZzd55/Z1fXyn/zV/5+nqe3gxBEWPuS9xyk2RnHkWuI6qycLimunBjQP/aCpMV2I+r2LhzkRonu3ixxZZznKQ5zZlQPQQwyIlnnXabdU10zZnogVrskUun81V0yFCgn2ZX0hlkv/kpN0/kOBlxVqNAgrA7Zk0v2e0Hu/tddlc9c7WGWKlQQC2XbHIsDYoAjGDavU47VShffvYrV176SlhcRY1N6xiagULNS8RkROntS+4RhH2dxTNBRJB8I+mKY1oIEORyNnhnxub4Ro7OacEGgrUQ514RnBoBzJHVfSM8ccLP94RCppD6uiAy2WPWp0YZQfD9hMZjARGnY+GkiIRXTY/Cu4STZw25Oo8buz9u799D8MxSAh0TM4UOaxLTYYrNL7ZSh8YWdMCtQnOuK9H+XfSqURtGD7TZOH54fLDbanTpFDV5STA3gwKIASkop1fPK/l5tcI+t1ShkECmziUnnATKOh6rcvk8AzO4mcNTughYWfqAxXUsTRtFtzFzK+h52G+MBuVSbVMnJs5P87T9rEppLSzDWj6DOvryAZ3hnIO32TbONRU9qNqWy0ZqNrLlbO3ZWXqbER+KL81+73d/+1+/98YP6Xv6yHF7bdYhWNpkoEfnx8wQhViEELQenCylnVcjRtV0UzQsWh4Y9QaDg7YuUCrQjJQTBbbt65CKP/69b12/sP0LX3kpZ0Ub5KqpyuXZcQc5B/JG08CRCqySnUbuyuiCDk9LebQ3Ro5JdvHRH1IWdHT1tSxrPFQT510gnGWhLsNoQeMW9cnMWjkSalSozSbr6eKFbGFn0ttnOZf1kSKSHFTrJalhOsiOkoA1mLu7bYiGI0sZkaIqNEOFjYl743jcY6A+u/rKa8987hcKKxcZA7juhLdo2MjQXgsyWlitHzFaUjMu8tKjD//6CIVzAp/3ktMZ5lZMjMYXaBVi+XmqD3jlvkili0munq/48NjLsJlmkAtldvPV4NY/0PW5hHngGCKC9F+5i8Jl1ess3DkXl44Y3PkqpZYDGIkh0+D0YXvvQa952GUZFwleBlGG9mNziLW2FDADQPMilJYT+1ynHKzCAV6icsijxf7V3unetN9nLoYcfDzjuhcWu6W4QfvEWLReTFbKdB5huZov18r5AresaCNcJscmP1Mdg2tNaYPdTFDQBEW3NivKLCuTTCaMsAdrzUgfOH9zzI7VybQL9TA45uB31GrY44dOsw6h0LI2Dwvp7BCH8NkcRKtfKtRuJDObkhAhlQjnP3zjB2/80bekPtIfHh0dMXXsdrUTl16RviKXKzIWLo6CzIBiKM1GE/Qt2UtAulA1ZUNVd0L3J920VC6XKlZZK8sgk2FjVH/8zd/5DzdvXHrxcpUJE7nPlLYng5NZF11nFTY8wcI12+OhB8YUcDWtG2IfbVegi9QAQ40M1ck5cbVUrttp9ftteJI0DFjxz9URSpJF1LQJoTYBZg6ooFw6X0M6IhFLBuWY+TiXGORBJVFmloG0DuSePz7o16vdTJhpJzr9FkfCdjiEBuWk1evXnvnyf7Jy6WUuWnQktcwxojAeoyEoh6+MzuTcyZijMBEWJs8DWLy7TDw+nCdWCyUGjqAWOJap2bla+KVX7CoCA4Fijp4YTwwU+UTSUYN+0jcCU97wXeA0zllY5W+wROtcF34RjuhrgBbAlYoGRdL4iLndY9Ka0GjYObzdPj3qNRrsUkMbElEn8umwmGPagtoW/Tib4e2ACO44aWk7LNJUzp4YN4b9k8mIVnaEcHCcDvrjgI22LIlR62SFJWKmgmsryfoqB8yKRbVezMgsV8zQA4ZJxIK82X5EK8TWWRY5GFVO2eeNdrQOCLZbWLgAIuCKlF6/v5stbPT7TfTAGY6OiU9HWlTGUy5Ig6gLoyH7dFnI4vQ0HchLIpE3pvNr6eKV6TzLIaXcGHZytP/dP/yDXq/HUHMPGdL+yQCNIM4j1iKjWtYwZK2+vzKaF0qJPBxAm8A9LDPWPgfcncZ5ca0ep8U95hRG1RYb6otrxbWbtbWLxXrh6MHe97/7wyubX6myywJlxZA++yLq00pSyKG/YGOkSQNB6dFBIb8EB3uvYDxGqDRqTLvYapvPF2s0MZlSKdfvDNr0vHTLnBiijZFaCAC15qtD+lHmunPkpG2mwCiD5pi/osQ65XwaWjMW5Fl1T3GcYZ4Gg2OiTpv9fLLLGfvozxQYU9cSKxu166/+0vazXwuyZbBqEdDYaJm6HKHgHhtkth5H0JG70VpkiV0XDjGzWpjoJZwqBItVjo6lLX6Lz14RtAPQ+0wYbArg3OwTBZWz4VKY5eEojsCQJn38I0A56iezHvtGFudkjnGo2A9D7OhCOi85mknU6CAUrdywGYdTRzrnp3283zs96neHvbZm43POAmNVDdqfSdmKU8Xm864WbzkdaNyAzpmrhIljRKfJJKdFjBmxItNAonB4ktg7maI3uraCAvYsm57X0alYmZdLyWqtWKyU2B/Bch4ko8ZcwhVb/oX6mGGyPEVpsubM4AbhfpXjhtLS9dc97wgZ6IFL4xEb3gYpbupVr8eId9RLFehLOIdm0H/A4d75bBVUmQy3C6JiTh9QyBQvcdkY+4BYUx+N+z/68Q8fPrifTRcPjg6O946bDdZfrEisQigWRpxs9KffQsWVTohGods+oTOim5knOQei1x2QBjRgudBXDDHr7bWbD6fN5/PXn6ttrnz8w7cOvvha7XJZHEW/m6uP8+vz4R5jYiTAdGRM+HS8L1uTEUFOupqCsrQ/5CJBNk9S5BwnyoJ+2U72SGXL9WFpwFZ8lFIYx6qhkHxINcgioPpSVj9z9RQ3nIUFVHY5fduWHzj1QwdVUAWsr3P36CaSnVR+/6hDf18vs0VqXllN1be2165+4cILP8vhP9KioG+HKkDtGMwRjFGL2oazj0iIf09fMhi4nKNwsaMMTz6AeUgF0k/4PJn63kcNufwchPNzgaIwHocArVtc+DrTcry2i0KR8MS+DpFzxKw2QE74G+SytwNygZ/epMQQZnBhDc0iQrVeUewgcXligAbBcTQs+4noHriGiG09bM4BmP4IsQXbt1n65Qw1JOD0TmH5ys6zXw4Tewcf/jqrGKBBuscdgjTqJ6ezRnteqyTrFSTgEKjO4qtU5itr6fpqqVKrsNCeKXAyO4I+06oSmzNrYXRl69HzArMyqJQayNVXqhuX8uUyasSj3tGgSc/DDje27JA2Tt1kLxVtAVMkCRyZRbWbD2jH0wG7oTqlch29ZBapYZNUZiOV46hMW3KcJzkY++03fzhCEDqb7j3e5/hFWJ8s+IJy5YuwiBvb+q1MOs+Ij5aCxc8Jt1erp5wPx236QDiQ4rOKtKDs9jr64Dib2l59jXuXHu3v3bpcUecmsT7KZuvD8THiFBTjuGlx2Gcth5rW5kjO6ejDkGzRSKQ4NquULSKqQYyEfMhkTghIElkarkTIGJIBs62fc1+HEsqRbKwhjgdsZMwHrL4UVjj2ZRY2NNSdzQs5RErSq6NpTLBdOD3fXiuMB/nBZMCO63JxWl5bX738+Z0X/uLKtU9zgxypBaky48jTkaIi0oO7nP3HnKIX7lYEZqfmnLv/RED6PuF0FtsCiQsk6sTNgIzDDEMM5bDJ6uvBhbK02KYq+Xi36LPoCeUj/FaFhoqCjpIYBVtEESH4076EjAPFZh9oERdFawTn4jE40pFi9W31xfHwf+o1h8i4tf6TQlTATZ099W7s9clVs9UL5bXLa5de3Hr2sxuXrgx33/jR4K2PX38w7LIOEbCziS6Us663N8JqFXVGGFhiPaSjUAPtLlNFDvCdJWvsCdJqP2MDVKroUsWMyCpYfawi24c/UY3OlNdWL71WXkG1hZsIh5PsaTL5fq91FwE84PSX6LXRyzEWgzFYzkIDjqaaLoW7NQv5i+z6SWc4kFqSTq77myfLDE0hRyLtdBqHu48RCR0fHZ8cnSD9JHoruKXqMLvUX9k/hYgIfedRh6EjO5She85cZCasajYyjYqaDvO023gwGNxKzPPNRhsuQ1hElOhAcLRHos8WZw6SYgtHddgLu+0DzpWA7GknGBQw3CyUqtlsAQkTb+bfttrCoiOzbJZFWPEvcSLFuNu3Dbycsq9JAo0cGwBZ1aQoEaQF3GaRyk0zErog/KQK8uzzZBsW80KO2uYi0PT42pXy/V1phVa2L61de3XruS/zS1fWaBKNLIzYjTZUWI6ePA3Z50mXJyl9Cf4suCuqqMCMuyyqp3SdBPQ8GEXrIK2vw8mHi3sUpRUiV3y8fLRxTHGKYiYU5FLDG4WwLDvcLgxgHlmM42kuS55nGN9Q+bRGnAcsHY8Hg4h8dHzD3M7LP3Oyf9D59/90EjS1gbRWK1RWCtX16ubV6vp2Zf1SZfNycXWdNXTaVRrk4vbNCy9+6fG973JCU7vJ0YaJ9iC5cwG5C7OVIWuHQw0t2ZrEoc4sSGQ5/StXQ3jAssUISssEOajcLwmQANbCBZ9H9M4gq7L5cmn9s8gAubKXXjOZqGRGXCLdHc0Pp4j9pvTMWTZ706UgR9HGPnopVo0niWLx2urFi7liSYNbpKIpxmkbiHa0q0PjXkaT/X6Pk9Fy7Uanz3EAKg1Vt685V/NWRXSG/UGTISJhRybpYQBsp+TTgmhfAI+1uIh/VLBaKAF3qzNZrfc6DH513gPdNF5IUfPlq7PRPt1iOlWsr9V62u3eZJEGDGwA4iAPypQuF2WUECakY4RErHokzcGC5mcuxwUeWsEYsdIoiY6qVap0jLy5IZzleObcSLmQv6rRRUKTzbLsoWrGp1BB0puvok3DAu94UNl55eLLP3/x2c9nEThL9O1jI05HdbHBsslLj6XIA8RW84lfLrSBWiMV2V0BOwQO2Bd5BLCIN/ImKcAswJbZ0iWTsGIlmhAlzUMKEQ4KuUiqw8nwXQaglWHHtDGQEiI8+kaPgUeW5a/HY4E/ASgGsXBEaIgVg3xILPlhBuCwYqSW2ZT+6i//F8/+1F/R8Q3ILJGaoMrJjgaUM2yVltEmfKtlLmWPY8aq6zd/+eZn9+fz35h9eNTtJWu1/NqFQphGKX/eR4dC11SzOpwolWcci8BsEuopVvLIY8ZMibon7Oxm/ZxzbpluQcHaCB4W6NjS5e3S6meCcAtxjdhQozo2yz7mrjDobdo/ZGWelUkW0CFx1vdYamfdjVOGC7nSjWeeLVU358Q/Q9ExDLJsCCrRaUj5zfGhJobkYNbvoTEqMqY4iGNR8sqcKxnemlJpCGj1CwwJYkuRejered+OaYqmIGgMMJnsjze537SPpp7W9ahrtXthdpXpIOcJopIaZvPb1184fPhx82SPeSPNAixUqa1xW4ZuQWT1gZ4Q9RrqRUkT7WHQ4DOTZT8hBIQg1eoUOlLU2mxBTaXzzPq0Idh2QxCAdkT3X0mlZc6VpIVaZRyUc9XcaMKw5gXEodnaFpUkDCIFRyQyL0pD7mfLx6zmfAaMIIbE+XgUHs8ZjOdwR/DR19hHljPYsFsES4F9ml1rqADyi/3FgdHjMQHp2kThXgQDypel4CMOwRhhcBwuTz1yNYTO26XCfOzlwi/skckXgT5GS2dLWQhJEZMrzuVM5S9fsFIwXBJ98KC4YnFbVSlFogDIL8iv3Lzx4n/KeQ7D9v/Un3QKq5XqCuKW6YhTn5KpHpKaaaLIoIhDasMJZ7oUWW+ml+x2JkOkfAVGZhqIarTFyj5r4fAEQ998cfXFVHaNZTDdYi3BByO7Yrp4HZk+yVQLMjpFXilxP2dbTFlI4QokqV/dfPVzG1deQ/FtjCLLPDNLraTTNSgUQmcRnMzovEGmqdl8s9lnd5LLlrJi1aBcURYqHisUsVyI7jOLH6SCTEsljBN30ukRygKs66gl9ZXpwrB6o54NASaCFkS7cKvhBq26wHRV5wibLCWfX1u7GJKm9jF8yF5Mej8xPAs6SoD9Ky2uvmXXggbKDciFmEnSXtGSaOlIChGAsebBCII7vYvWN2qezPlanPc0nrWmnFXHdcfDfonbNrjlI1GapdbqW8+w6Apr29jICEoZ14OFXCnDUe5w9H4RgH3/9BehPDYPaxHJadmOmUwssC0Z5ezAeau4LWkxqJWPpdMznQNXkCceoRETOj8rNdlillAxRliMKx0GlcoyOqwLD0zezz5WgvZyrgarVJuBxOrLPyn2wRYOBiJfkZV8RVn6mVmhLKjcjKaExqoomy5s3dq4+tNHu7dnqUfV1a1sQXv6emF/joAcaQvZ5gahcJrmpyPAUAWDNqcF3SqhJTM2q7EnQaM2Hi2ecFf85Vz5Crdfw22045YEZYPNBNnidpKzVGZdjshg0gq5888hfM0mN3ulP/W5r1x69utcKTGephUHMYdV8JBiHuNDdWmMGRk+nhwecQOuSN0y6fKpXPr88dEGkwkSft7IQ5h6IqJFI2fSK5UQJKb7vWOaDLVdKmMVKoO62tqVyzdeLZZpyDJqH6RhCfMYUgpMB/UzH7ayg9sKbJb4bGd1f9BvUBqZHEduVEIGjhp/09yQTMfhrsbgOOZ1QbZY0LWLlI3SRDeGGg8XpKKPQx/Iian8WDnkcH24sJrM1AqzynS2x35MLocJ0hto0XMrRnX1xtqF51AdZPhByl3Vu4+9eS19Ixslotz6R0Unions57/yj6AxM2UHlNgoLhWBJ0xzlZkm0qrDocHfkIscl2IwnI4WMfIY2BkYoBXA4jKQ5ZfE7sv2M2b5+CQsRSmQJ/JpqbesO8iongw4Sp+PiWzJWY/F4Iw+healwnC5VKp9mS7yHQf3IR3VEgCDOklEmaXKysXXLlz7MJ1+k2WtVJpF9ia60GiWoZgPbeTzqGikOE6C+7po7ImjWOT8Nc5dT7CJEJVUGxXToDP0zEJG6cKFkPNpXJJVLxAf3MxKQZpBGoL4cLyOcDIYc+kluxbVj164evnmp79ev/B5ekXEmINZHoktpMnGcLpT9U3I9RmZSQtNXSIcT1/NPI+8M1Yku5ZjHRdFjrDTZ5EAxKeIL7mCQgMC0kOGtRzBXCu3WtpqdXutkzuc+wigSCURVjee/9zXfnXnyrU2itTp3IjlE1b8mG5bNViONC6lAzNe14wALYLKuu4NBDkx2zqWpgYCdhVjtYMfhCB4GJOJrhS5sXK/i1o6DrhijshcGpZDdy8R1hIcl8hAJV1NJleyXOmC4l7/cba4ns7vsPNzZePW1q0vVDcvSiKmmYhi80zhK1w1q8dSbr72Uu1Fj4pMyXKk5V0t3RGE/JbdLRoF8e72iRFGqF1yhNqQ6+XhnZNSa7BKow+NXVAeLoo0QmDJVCgcFj3hEpTHggs1wHMWj2zm4pwN2F6x3cXs+chBLyxCeO5xAeVombGUuWxTvSodV0I4UTkuLI4yOZssoiHhcfMihAqr2/WNZzvN+4yt2A6jVftcUK4y/ET7Y4auMIv4agnn3CO7wflF3MtAj1IobzAJZPs2halTMll2pM4lVOB4GSKAbeThjhOlxdcgDoZjnS0oZfLXEaWGmZM6l8pvv7Rx5Uthdkt6nuNuD4kmzJXSihmCUclOmT/ChySFVM9nbBpCMl+vlRsn3VGjN0TaqMqk1LQSRgdULpYgc4Q2Lp8cWhNVDkWANhy7+5sbl1+6dO3zDz58/+T2dyejBsvlpfqFay9+sbrzzEmnp71GlSqrCGgrcHqUik4Ea3+iHNJBDq1A5S7xkbYpqUzFjOJHV9p6W0mTOn1V9NIbonrgHlq8RJZlUrVSaOrRhAWlVG4zVdhRcSE6DuvzoMy6fymzNh7cKKxeWbny6urlF9h5lCnVUcaFCS1RFomLUUmwmCwFliK5KNHy8uByMqu5LMzmBiLvGZmUXznxioNFhBahcaXkvaP+L0LggPBcFIwrUBBaENnAjcW/LPlKt1zssSJk8P3Eo5AWWqzsgZeB8ANb7GOpMH8h94bFR96+rHBcADvIZUQuiXoDpRTYI5QkhJcK3Uz4ezwOyMEqyyRKjbG8g2Jp7dpnu639owffGgyPWPkrVKvzySnbcWGjtO7inLAYkedKvfFg1GaKgn4im3u1E55hmyQRKBSzcWbGxS9VdgNIFAiz0Ysw89JOVZYNUcVG3bTKpnYuUaPFYgd6qOWzC+nMqjb6oXPJVgtu/uU8Jg6BUJdB8DRzIZNhkFbSrI6M9UPO2F9dW+WIAFyanYH2TqhDndFDrtfW69Xa0ekxPaYrFLURUQlRNCxUtFsH5X735WefW7n8/O6VF9gEUtusV9dW54nsw49uj/rtyy88U6yUNVbWHkBXzNC7yteXvC918FKi1vxSlI6/rE4W1ai4VcoEVuGr86SJADV/CNTASFkxnOc2VZRdM/OQJf71FOf8U8hhjXPT0FUobl8srV2v7Txb2brMHiWdoqd0uJ/lMu4BcONRSlW/ZjAXRb5IlAeRjwIsAsll8UTlZq0Ozi4DDr2grEDsBQojOvJErpxTBCDI6MHHytMSswyGWVZSokh4hMcZfBCzMhw9/zjwCPSsL2Hk7dtHIXcO0de5nAkjeJXVwtGnw4d07pZ+Gc8UMXaXSvlAdtg8Hi1pPO1xpCkgJiSFC9evZ/9arrp5eOf3BuPb49Pb3LbEVm76wGyO24QypTIHOHU54ouLK5GMhGlEF/Aa3JdDwoHAA8EM5Bhmaqwiwo34SJFYky5Sw54H1vP5K7G0iNay9vuFLC5zmhs9HKebcPUse1xZQGfyiYCHgOgAZOgQqAzyAh+6yoHVGNSyE79SqZbKTV0ZxrER6IyxhMluOqSTxSInh3V7XDkYVacjDF8+DD1R8Rns3fn+m9/+7dql57L18saVra1rXKKYfvO7P3j00Y83Ll8traxqUZ7TkNjrzOksVtbGZIrOl6wVvxjcWMkmxHGM9Id+ZKQAVn3KhkIiFtIAHREveuBaYOU4tImWKzT2TlcnyVIyvUoHiZ5TMlXWsRTsZ65vVi/eKm5eQVuXdRrF7BjM2MrXs4sGL1GxIxxLqaweJPaIk+QBlUYP5EIapF6eWyKOcHBCJziid1GciVCZFACvCFDBZONPToZ3EbVH7mKWrysqh0b2CD1f1qM/+VGCF77eaKVlHkt+LhWWfkvkIpRMKlYHccbditbcrdwdnIOwmO21CCHKdVjcG5+lFBicZZEiAR+r3VNYY+vitdpf3rj6yunuGx+/+U+OH/wehx4Vi3BgWK6ge8bxs4MgUYELYI9Mumh7ILi3EH1UxKd5lEUQ6adZ1IYsJcOEVugb9Kdt5+m0lhYzZdp728vDtZ1wLisnSBRDxCfcWKiNp7rbjEfkqbdGoeoS6GQZpEpWz06HVJLLiqqVAmzY66Hudgoh0D1C2vR9hyecQcYap/pwglpWeYFVBRARRnLUevTBN/7bTIUblS49vv7a7oNnWpzn+OZ/qKyuVza2VrZXSTfj7D5XVSCKUWfqWma1XNQFCQejxoI8vovUEFPDQ8lfozo0fxe7gyc0BtZayRHDUvpO1Nx0+IvYMRtwgmO/luB4nnSfG6ZoG2nswgwj0iq3a3NiE2VpmeEdVbBScK5uvdUgPIEL6snnXLglAPN5qjfJX3JfMvrQuJwFOWszX0AVUD7GlJQKj4pWzvbyZo/UBRDsmTmhi10l6kwL6CdNS0AYeagl654WHhESvgbiyi6yxSgVIC593xxa0Ch8DOkMEYJlb+fmosaspgy9LpYNWVMuFItXny9sXEbTpVTa7Ox9K5wfZ9Mjdvfp9hWYYdzikhiUuGEgbsJk+Q+9lgzWKfdspcqrO+zCJQdglPyE8/not2DvHM05uy3KMAs9H0v8vEmALsVFqK9DaNpzDhLkSsSZLnuiOphoQd3wEGsAdHGaDjKTIpgVHrO+9a31zmB4fNplB9KA/X4a5KWGYzCLuH0purL0ZSJeEWPCLNQzpThocq784Phua/f9hz/cSgzbpdX19RvPX7l1Y221okOFp9rMNeAwK7JhJAI+iAP0JB5202TUMOFC5y0hCXHhYW2HgKyUrfBFQDzkDAPqpaSYAtK8XermrOaiJcOya2WaX5kOT5NoJqGKg7INkjDuBUAVDhaVxNVRKPwurHoZ9jNESBSOfCzG6OXgfDJiy5OAluQokNDrzzLiPlGMDgR8T6LwoV2OlWVzUOmTW1UAdv1bu0YBegx8iEI/PPnYE32dF+23d7fPAk7onvosezgzGGVQWqIQ3mDOcvOROuco/87VecUhI2Ac4pRGWOXkI1s4yRRDOoNgCK9qwwuqoiBSqUJp5dqrz2eTD9/O9XZ/L5wfcSXliPOfUJpkq26ylOOqsww7U0f5dBV9D5iE22JK5c1sfsXHJpxSVgU/pzykMnWWssEsEQsbLMSgsBUrAAwidR43m+h0JUtilslwdBq7eOlSOaCJQ3vdsA41F7pbnaOLuBMmYzfdxto6OwSbJ/3dh3tg6LP/XxSqzoWvMYsrZddpkRLHOy6BSpnEI9jQp+0+mHR38/XLF1547darn758cb2Sk0IeeGgrRrN5X2sxcIBKBzYjGNdItLt9qT3QMLjjl0L6e0TDIef2MJ426afg1SAsHhyEVnIcm9eRPXEMA1SaOCIL0xzgEXCcsQ4LhqsRQLPztyAmZN3CHrpM4TAmVPXxR25wA5WDiIxn3bynEqBQgH/iYwWnpHqYGFoGi8Z5PA0Fbj4mi8YFtcQqneR9OVYPaUDiUCseASxByVn5w4+odTTV0x4XYBHzAsMSKhfQJ+lpWJyby+IZfyXMkdMiCucmsMjNYXYOitbc7RWhXE5MZFYodTtGCsqrBlMEph/ifJbVQu3yrLMxH3ZG/QaL50zAWK9m/wSbmCQSTNEfIh5lmFniUCNGTSablDxfkx6oTNvN2X2/GmTqEBhHabBLEB1urpdhRwUTLjR70AtIpXRrUp+98e0WW45y2dwGp0flUArRyjwRkRzWU9TDWUcIQ5BQhEHFQv7S5Z2Do6Npcndy1AavhoPkzSpNQPopc8qmL5So6EBqgJq1IgLKVreuf/rSzecub2/UOLBDM9ikzssYJFGG1+VOtoOQRpvOiJCESWU5jAIwdX6kC3VyFiMbaEWgPg84t6pyihb3JzHBVfwkQpwis3WYhNNoQLWjZoMpNEVO74fK2zRfo4UCgFUizsDgch6mhQwoXG8LArVI+tPj3jJZLEtOVvFRduX+5LMI/KRfhM5gVAOOgRR9HPkizji8BfN4LXLLo7y9jyGQTU2lcZdlIsqPQyQfeS89KjlcxYTeQ0ARwFmn2NkgvF8EukDgXJbRPAXeJcMaD9DyA94wkgMznE256jNKlpJBQBdmKfo4B97NZU3BrFSMJmyAAMXb4VthjpvrdVbDlNkLxzEUOB4XdmQHHht5mByms/U0e+HoLvodjtrOckgTQygpgsCGuUxuJczWrAfjcnvUqNsc/jUcdplqsQ2fcSzU9vj+Xvu0kc2nV9Zz2yurhTwL9CVGaozW2LhHn4nWGTM/9Z1si4RNhyi5cWZVWCwU11eDnZ0dGo757PHewYmYlM5HhQOFiz2wx3VgBolrrfZ9AQCAnuf6heeu3fpUrVKFH0zHzRYfxxx2NmXPVWianPAfRco8Fa5idzQsiJ2FCdBSeuyhkMRT6/viSZDQUaL5w42qbBfmIVFA8gONRL8aXCqgejZKgW6Uw+rYXZitsc2f/l5yLy4kZlM99/tyej/qpLYO4mqe1DuDKjp6rLYtl3KJDbIsgy2sDuScn8D1CL8oyLB6DAK1QaT5y4vsG4EqRIQo+kZuMYAceBxeM54DFULnTtIiPxeFegeKy/eEeCvxloElUAtrzlFggcRm7/0UKOckdGo5zj5y0GwEvDYLkMkeK5vI4r6GwacLF6ziLPc8gdgSZr4OROwLEBXvxk8QhsQPNNqauiBDoVtjhSHMl1CjFoNpkYtt9eUMe3RQOma/E10Dqqp0gOJAdP7ZWlDLZKvwDhuZhvSkQ07v5hj8BqcysvbXPm2zZE0Bl0rBpcvPMewacuE9Ct6opaAErgxKnYDRKh2oRJ8s1jOfZFmNrUtEkElzygZLGnlUp8uler3cH45PGx0VlDJn+VHeoABP/VYcIu1FuahwU/Xa1rUrNze31yvVIn2eTuVAhJSDy4ISGxtQLmcQzZkAlAgr6jZbBS/pMcbyyOE9Eok6GhxoJYlGLfNUNQVox1EIqg6jVwJKJsV2Lw6jJId6dNQW0mHOqeM0bQGjBM+dPBqGSwxGO6EyJ+3kxU0HVa9WYfaRTU9U3872dBcPRGA9/uMsi7ey8HQ/5dd5WVBsDgUGZ44NgGFexmKQDpxC0ojAKksuFiFfPQ6CtzG41Zaz4HJGMAOsi9OFomTUcixiVPQ8y07OZfntYjMXasIHiQGWHBaAC5PiOh8EFwHotQQYY5TrU0IpkWfAadBJDz0JAyvkkaxaq/45Yp7Vczaswnhsiue4xFSmBJVAjNwNBK1oWUKSBH7wobjU2KYLB8Jg3fYRPSHLht0Oh5J22Ix/8dJWvb5K53F62phxK325WK6tMadiNz70jOLy2B8Mo36Ec6NYzSchLDdSL1If0F6rCdEwF6uvXEDX7OOPPzo63nelQoKVKasVKwACqMvErKtamOayiJ9M1MqbFy9crdWQ/7KjFj1O+nz4nItzJugL0OA49lGBoFEuDQhhpMTgzEarT5eG3DfHTWTMjZNJpKmDPv2hurcsOy65G4lbsAFihqxRq/gt+sksitZQ29YqNHLNcZx/gGbflPLkyF41feooNRgnoEErT652+TqD5U858yjNbq8lUPmfeyL6PuccWYU/eghtVmJYitOZo0gEKwKzmFx0kdFAsKhJFBgvayvNH0dlTdMMF0oQemxKbfDOSsFLbS12wehjM3+fxIW/j8R7nv+cjctnS2lcBvQWYlqK1wDOB5ejQbskncESYVwKYxEtAUGb2CwSKpqC0IiKFQGOTWIBD26ARtL5ElqPOlaMtXjk5mycyzIP5CxZ7Z2QnAFqcY00yaWPs6OvR2wZ1oH6zW77dNhtdujqej2W9Tmu+uLlHST1B6enbLRb2biKVhz3WIxQJe9xTK4OfSB+mFAan4hJtQEIOShWuICTZeYc68TVEs1WBz5h/95KdiUIuDeGEzRI7xBu4gR+OtFOv0H3aQXsKY7de+g+ozPAcgeLc2ssR3CEI4uNKIR32+CS4nSKU/Vz0zw35ZIMhMEJLr3RoBJKoR9DG4kmKRnU8wwcOVl30uXKN630Sc2P84cbx0N2mlRKqQrK2DRHmtdSvI5/gWOUQEOlfhLWovDZOMwUlGknK0Uzxq8cKYMG+SSf5BomTQsL7GGkgSMKitYI1le2I/eohp/2Xap1RyM4uLHOU6DPAkcAzlUxWtQxEIbYbJ6w0NO52pGaxiBqdJZ6G2MhsAqPskZwa6YcYsECYc4CUHw69Ue+QrR4wBylxbB4H4Nx8AvY2CQsscUMSsbTHyo+grXECsojViBqN05AhOCTkUUQEUbscVo8UlCydsfNPlKIyaC1BslwZ1OQqkk+HCIkqHFebUqdIZpldugauydsAqZyBIvM6K5w9ANTG10H3GkcHe0/PjnYd+L99c21tbWV42aTWyW2ti9mMyX6vMbR4eHuPieGMpKtcOa2DphBN45VOoqdM4sRu8CcdMvqmZkVcknGyclpm3s1tE4vZW/65EJx9eaNV/q9DvqnINIRiMdso22qlTEC5nTwemWtUqrDZm5UzeYp2hX2/XaaJxziyeSXq+mR+nA7w2SS6RE/00BSABAMYdqkMBYKeJ3hpNccrFfz1WJ6YOso/cH07kOyNTjZO4bRti9emE04uIkuFjZUAiAwUalG1hQ7DgzHeOjomEMyF9Uh9lo2pT/maFHa/CkHunFzPZcU5CQpUq2pbuP6tf7D6tQ7eQh93BObFF8U0swKsUyxMdIo6NmvWmc/VVl4OOwOU8QrwqrcLaDMhF2/RSyiWvdn/hERe3qOrEuYXNCFdDSOAQMFAerY5cm4F/H6JOizDC8Ah+hcYFnPwS5DGI6l1BrSZcQx8CIJ5kTZL1JAakQjxBRBYUDSp2W7Tot7HQZNqI++jd6JmR6HBHJotHafhlnOYsqmstyWjry0wBnAcBADWRa/GENJsDPlOMzTo71HJweHHAjB+S4P7t27euPiLJjev7/74ksv53LpQad72jtuc213n+lcUK9dqNbQmOOsP4016YDRyIEDuT9Xw0HpY3MGDV0dfSQPK47YOWeuwxmAnKlUW9tAy7x5us+9YUxlydPGyoVGK3/S3qcDoHdeq24Ui1U6p7QunyLfktMgBGFT44zDyxCQMERNpYq1MuWqfopyQkY5TrS70zkSSk7joSEIA3T2yG0vTH34qF0thBvrDCMh8jRHUf3xt781753UOcef81eTl+1yDtTW3fKhhhk2xFV3IHUgSt6pJ9hYjDYLHQbKTvM/7WTEwIAWKZD2PbuqdW9XV9RdVGlWsapFGXg5sMjV2+XpaNUFi+g2rvoF/BMmUS1wHq8iXmZhB25YxRAxMQHuorIv4ZVvc3KwYBFeywjuapLUUstT7kq3zDxqRYFeMKFPisNm/gLz0DJ6s6V4KR0+BwbhXz6QfZz5KUAGu4RexiVrhA+n84Ed1FISBGtwMfAimwZMpqD2MRe6duY6yyjJaJGxJaobiApSqDVmVlL0hBlukkGTxNCxv7bb6rQlzyMBHK1bLK0Uqyt4oofJ5vRyuXA86n/0zntok52eHBZK6Z/5ylcf379ND8bBL/RsmxcuFMpbuWK1WKqgX82gg96B/Xvc1q1ZZa9N90dccBUaaj1ub+d0f64XlrwyjdSjkKtk8mUGpUzZ0CPVWLRY5t5LqhQVUMaffW64HXH3WL7EoBrxJBJORJ66wI1H5yBz+tKsNxwnOrN8mfvHJJPlYIz2IJ8uYoOducOKuWuBZbwUZ8HRRHB4d3JzNV+v5feOeh89bKyulI+Pp9/+3e9971//q2DUvPXsNW48A56mqZBnfQE+omxEbJrdYVDPYsUHbTFh5Ge1pD3X8KEuYErTolGjHEJjk22VrnHtk7WsWrAHhOcpIPLS19GBxbrs/B9jPhfYYTyLgPzxc2QVJ0iUQQKWgps9pmM8Fj/DKtAIvWXdwi4xoWJVqRlbM/r6U54IlwdT9DF+jIbdOcln8XiPyN95WOgFzJ/FBIJzSTCU5MAhs/oVIgNEy4XZGnMQdudXKmtBIs98SDMbbiDjBj82TNgOcaaFzGtYtpD0nHNkWGJndYwTX5jEFFey2TJLgjPkorTto97Dj28/evDg6OgYFdFsqXSw+/gzn/30xZs3c+Xyha2LCAaDdCWXK+09vMNBwmiAcnmwzptBTw2Sp4PSjl6SF9AHckZ4v88hTrNWs43qANedFDlhJVlCuKgczCeEn9J7MOfSsfockMPpcJzFxBFmHLyKbIn+TDyoPbr88yLxDPuY/9kaJrNgHZjDVYqDYaXKJVfozUxDrniiTdEWd/bVh+3hhLsiNipZrk+6uFno9XIP9xvv/PDdd7/3u83921yndu+j2/lwXq2t9mvr3T4SXG2Vp7QpcuXDip+0gZNBA6g5HY8Sn7GVnsi1L0oaACpbdrBwLLLRmebDVKTVGQZZXJXZ98/0ckGeDCV0T0UQuYqtYoCFyTFV5KcU0WbFHEiAOELzIet6HH86s94WxDpAgjpkDhV5pMkCBwF9iyUmXEKqIiU5zkXpcg2dsJrrIqnm4GNWCGJY2Az8E17gdoiXSkDG5QblE4Kedz6bmthXyCwSywYJU5bIsk78nDLUnMFyHOCtUQClIfLVSjU75XXGLcNF9RmQlcpCklRgEDnACJN5gDBT91QCfLy322wcnXKGWbN7cMyZFJ2Dw/x+o/sz4+Ev/ZW/vvvofql6A72Q+7ffq65vFooVTuoFh2SjUhhFMoR8IsU0r8/tamw1NjW2ZuP09KSRSGQPT04LFcaVHG4F37G2kOFCh067FaZHlVqNjhF1gHJxvdVuF7IFxs7V6gaKAlyAJLKXKEVH8XIQGxkM52l2ryOeoXdmJpfiypd0wAYP6B5RCkdRAa0Thiejao4GYv7ocLBZB2WCo85Hnc6/+/V//O7r381kyyw39KfZw73DxvFh/cJ1ziVncoryG0Wrn4ZcsSjQegiKDQ8YkvTAh1MWXClXznjSNR1Gj+pApS1iYHHt/c8wnENwzvokQkfeuItTzj7Oy1hO3gvPZbN3db4xjGhO/3LQP9k3ZnN5dW7m5UNYKZnzuZ7Q0MdozeZfccKXHY3a9fIIz/ot2xYx4uosav1cE4HTU+N0CFzMDsDMepn1XCAHKJzLyYmDIB3M5irZsITSGbvqTC4IiXA5miRcIkr+YVZYUTMrmmiC2kRG0gZpZBlZUXHzwbDNiWkrLEXMHj143GqzZZ9Fwj6H5qb/w79/naHXl//8L7HZ6c57f7x99bn6yiYXysxSk9MT9pKPOTtwwDXOOtReJ1zALPAGS+qcsUprUCxw6SL8meBa31nQYU2Sw27qKxscIdNrN1i0lFCEFf9RYZVNQfU6rSraA/lyJRiMOACVzp1d82RII14OiUPLPMnYGcZMIVblrihEm0w4ObMVtRg2GU8G7CnRbE1zyNy8XmVvfrjbGmxVs0hzv/8H3/74jddPHj8srKxwVFaj33ymfIlDt6c0KPTktojPOFhtFUVDT6spJ7GDTxVE8RkV0jHCiaz8I5WROhEdNAEof1WN6oqg8jTrMikYAhGBQyaTjGZzTrz9szC5eJfxRDD6LsGZcxyHA1p4O9PC7vzjt3lEL6V8GTPu4m/+9JPRfA0cSJdRbKIpvJ7GhPJYoBScRaJXRN2KwNkFK6P3EbRhNnf/Mj/Vi+Fx8BGcd+JzPhgpNCfn/hSA5ShkBtCwWW36uCxOubKGznUHuryJM07yiWnPVqtoklFfFBW4eQ1zJXhDY3HNbrTyao8sMBUzK3b0MoBE04u1DsQ2wMFfnGNfrZQYnL137/DHH9yH1288+yEXmtx8/ier9U0YgkOUmif7D25/uLG9Q7cEK9q0jfEiMyUEP/N2r82yI4bTkwNUt4v5EiytC6cmOU4mRITD2f7FIudQqE5LNe60LoKT0/vZLnXw+Ijt/FzUkc7m1bsx5eVWABhvOs2VVrLlVW4dZJuUtMGZIeamnEPB+gZ9K7s74FQuLq1XMkV00eArzsDhdu1Z5s5B8/HHt9//0fcZqNdqm6fNQ+aqhUQN8S17/4iCsbSU2jjVRvfsUjj0hJY42i1jLFiMZXlaL2XQOkOVpHaHkUamtJob0Ow5OnTEo0rw5e1MshmdWrUquPMWbcv2yTTxCT4+PJ8Io/CcAZafIzwPvAxgThHty0PNjwPQRz5iOajGB9ZH5Cg+9FFa+ySLGVRmZ07gFh57PPg5Gwit7ZKz8HpvfTCfDRP7mY/3tggF58rQ4XDhziIwGKEwTwcRY/xEQxQqCgROQ4DdShXa5KwUzpDW1qRRV/v7uNNvxrHteKPQCbXQJ3BakcpLcjvHgdK3EXlyG7eGXDwamU7q9ZUEK3zToDee1StlLi496XSRdhSz+ffeafzDv//f/9p/9V9yajy3ameyxXbj5N7997euXGdIzMZA9OKkr8JRR+0m8wMuaW+xOtjqtRoNJK9s8KHeUCCDQ4kKCmeFAYkLmxJYuazVq9eevVYolu/dvXfY3L905fKV56+dHDce3tltN/tovunKd21qL6EsXtrcCcIch9CwGDDpd7kDCX7IlUtBgeUWSVMYmhbgOnGudh4zKmcbMlpFR3cf/n//67/V2jtYWdtizozMqH14lF7nhPJCERkUYhVTLEDhRkMHtEytD6QgHVdRcuoMnaRU1AERIuKibDEyqpAukibkqhpr6owgVWE8Rib2da2pOX7Sy+Jz9HQGxOM642bkEDFDBMDXCMZBYjNaUZrtUWIisxzMbMl22VoKa75CAJkYEh/OCiXibMe0+AhHhI/KIJTQLx7vv3BwprPxOTdrjLzHJwSL0ZwP72L1cTs2WS6POJ1PRRAjU/jYYqAaWcpgb5dp544Talc5aKgymZwafUuaDx2xgoikQPI6idENH7VrRksYAyiEKXSE7HRC7aWXzbLemEWnrNXnWuzCIXdkj3RAUyGTXq9W7u4dPTgcX7r8rT//85lLl5+nyX/44PaVy7eQFrZPD2orK4hhkKKwXYktjIhetPTIzgY0yzhp3M7SRRuHxHDtrrqmbL5SBbG2+q9vbVXo/or5b3zjG3/4+7/dah9vXrz8xZ/+6ub29kuf/VQulx/Npt3G8LjRYLPQYMiqZ43h6HG7Mey0Ry2O6Q+5cKO2vpYKsiyR0LrkSpksHEhnjdpcYt7r68wpLhr+1//0H3/0w+8zC75047lSvdTu1dEQYl0zX11B3ktzwLofPT8NlZ3hr/uiGF7CXxo+SB0GyrPhPGVIn8giIcsRZFIFqduF57kicaqls1L2RKBKE20SSHieeIzagD3j5QGdsyE6T88LPM77HAJjhgUMJgOTixksSbG/t0W8HLuLi1zE5uXTYNQDnpiTwSgvyjoKCUI/HD2TrfOpiMDPfl1qDGHkIcQ+kea0wCqTQJd9o1BL0S2czuBZco6NEe4lLrPxSQywZHAZtqSyrub2v+vgXejA9Bg5BYwzEeEK3Fg95CA26VIjEXGtNXSisZN0S5gKcW0tp+KWuBRt/+Dks1/4zOHp9+4fNvECYr1e7w0Qc4zSo/Bf/vNvrK6vXL764ofvv1Wtr2TzmccPP15fvzDsddgNzEoGtQFmukTSgVCUg7dZpEeyP+x0Wd9XnNlgBRWAnc1nXrgFvfZQk2u2f/u3fwuNnXfffR3lVWj1znvv3v/wA87JfuW1L169da1ULV++cuPmhUu5bHk0SHXaQy5qyo2TMD/Ka+HKGg0BU0LW7o08ENtwBYuO1IHo0deZcTjwbPxb/+yffeu3fpPl0JWVCw/vfnTp5i2ExYzRWYS88uJrqNqiVMTMlNOQqSWG5a1OD3mT9EoZ8uphyCneomoYjqomST0P3MhNVkm2c7ChBYTqCgGhXtwvrjHjQKsuC42JR0RuA10hjB5P61idI28LB1IXKgJc+goy4iEHH4W2IBbeh447qAWy2GRwLlYXmeVGHbtSakm1JJMyl0qHeCkhsfFpc0LHyjHIUwxxSuS3hFx5Mj+XOB8ytviiie3n/b09+ggbZpKz1HC40l4UskPmEqT65NEnjkMGF9zCwFZsGtRxJ7p3cCqtqjkDUk5/GvU45lC6yTqZb8CQCZJi6kKrzwZeSAZVl9b4ECEKg0wobjI+zRRyO9s7z905fv/2Y2SoxXyB/pIzCzdrNTaN3997/I/++98ZTMIvfulz3JZ9cviImR6q1KPBgJElE0mWPdBuntAVjsanR8cHh0fEjplLnjgH6eqNazvXbrL3DkJunRz/u3/3Ow9373Muxe7hA3IElatvobQ1Nh63G8ff+r3f+s43MtxAzUj1wo0br33hp9Etv3j5pu6QGDYSnUMEuIVqhWut6IcRi9TWyoU8qwXoBqjnQqOcvo7Xf/jXv/3P/tv/Z+d4H4ks+4NLpbV7H7835GCe2ai2dmHOvamVejrHRWb0hYyW1d1xaCgnuSIKMgmpliBpzixlOj4AIHpaxLXwKwoTOpVHCtziaz+jWlSWai961GC7B4OvWFeTsUcE8Gf6OhQCjUxwjVAvqCWOyCOMAM/hd/FHqTAgZxEzehyR75no8I46+CV/3xM6F8fJ5yJcsi4S60vNHIzwo4ZlCdoZDbOitiJVIs88Z5JyxsdbACCsC7UclrxEYe0rNlvUms/sIgAwzF50xBkbGHTgfLfR67YO2LeQzVfZXMoZbGEmywxJTKgDXzgjTOtqULtROQuBXFjPvqdMPrdOKPanH522N9Y3M5nUp19+8Zvf+fHj0xNmAs1OFyLbrFZSrCIk5h/ePfjb/+9/Qg/yF3+2BGlW6rVRj3twpTmD1gy6XKwi7h9yAdMpODmS/vjoBMn/2uaF7UsXL9642WqPfvM3fp3pKMuA+5zGa02L2M+aGTGgEZFRptE850o1B53m8fH+7sO33z46PSxW6tduvXzl4s3VFQ507J7sfsg+Cg5V5C6AGdvsQ5Q5OSJqwiwYWROaNT/63nf/5T/826cHDxgFsJ7e6bXWaiv7rVOUY2++/MWv/sp/1de1PLl0OaeFSSNgEgEZkyj6QS2OUHFyIbtaJJH0ho0VwxE3Gc503EcbJQgEWkzPkfSiJW5ZUGULm690/3HWKIty9ACuIJ4I4uEttIf0mD7hswBamAyr4OPeyyxywEu0tgwrP/eouzOfp3nLEzARrfUEMiuiKM9RT2gc6BEufSKwJaeF0dLkrAuEZvdso9AQjQ/hEukKdYFkyRQBLjlZUt38wPlGeVyCdU5GChbSgwBhbYO3WhnhwH0qg1aj32uw6FAoFYqlcoYDZ3THNQShoxdgQkgQGkGKz5iQIKg3M+MbD7psMkTJhnxwdQRotdVo0OO+hls3Lx784HQw4riaRK1YSo7Hjw6P2AoLhR2e9v7O3/4f0W772s+8yu3WokhuhLHBmpYmUZXusVLYAZSztxkQVtfWL9249Wj/8Lu/+c/7rcHh0RFFQHPAYM5lzvLkSkiMp5J2JUxaffZZpmcPB0ptQat59OMf/MHd999nSHn92tUUl0a19rdQM020C+Ni2EerhjQilJofHOy/8+M3/vF/9/9p7j3KhKVpkmkestsx4+9ittIfT2+9+rXNS9fgtNNmd+fapoQutDraJ6/tT9Sy7XByzYPoS4KZkPJETZXzQrhQLkhwdeoQ9Z8MujYMLkSCRpeugnw1uZwtvSlt1aSqTy2te5Zq37tEPjHIEgoRwtKzsJiJ11l0C3+Fcz9AHP94flpCp9S5X8Rg8lxCoibJ4EXHlhfFZwYPGDGhQTnQhZGwgv+Ehzit+g1kOVIHfy6kK0tSZ4azeJWgc+BRnDhDffK0GKIUufy6MPZ2WC0U9qXU+NyqE0bLpd8e9U65B7pYYXkwzOdKSO9RVTMxOi+mNCjQMFJCCgJdwZBErjV8tk2g8sId2qQftedWq7m6XkP9lNvoUTt75cUbj3ZPbj9+VEint8vVXotN+9pDqIW0IHly3P27f/fXd3d3f+nP/xTal6BiOy80SmeJOLR5eoKYEjUarom+dvMWC+Df/Oa3H+wfsnBn/Qo1p0wvBhEiA1x8SVi9AiIgX1toTcOF1I2O2SYnmdQse/u9t/cP7n3tK399a2UrH3BDeKHE/q1RF/ZpnjbefPsH/+rX/8nJ/v7B43tMR+u1nVZzDx3ZzbWdWu3ynf3Xn//sX3j2hdfyqXD98ma322q2BuvZvPYiSqw8Rz3HysnYkkQgZSXzViO0CyxdMEtkks3eCZAzJ+QOGbTnM1xjLx72lXWu1rwrH0f2hs3yqHyqAFzly/ZneDwbOEhFJVTgtGJ1ZRchP4PaAMiL97RERoCLaF1SHE4DWXhFpgir+wrUGNJSYYIZhyPKlatWXzZR7E/mWUlR0xRFKi6Sm3s8yvPBYmdfjJE9+kbBz3yXPZ3Z4omcF5GeCUVi8HENqDyw0GRz9EkWfczKaHqaTCD3Z+SJVNTkDsgZIBSoyasVE57Hdv8OB4Us7FpGr0RbGdJJrsStr9dZUoCcSlzgsJL/8hdebPxeC6ZE/+uYFT+fFrVSdBL3Hu79vX/wr/YetX71r3710jYTs1OkI8gn0bfpdjjQLcwVcxeuXGPr4Xe//zoDVLifQazW3qx18qUM0jP04MpervqXJ29qTH0mZromJrCVwsXNrS/sHr1zsv/GR/fevv7yF95/uL/LoRuz8Xq9drD3+Pt/+J03fvQHrdYhs2VtNpnMuKk4W7+yuXFlY+v66+98l4PKX/nc13euXM5yeQv6OfnVvf1mrVbs9ZusTNTWqmi3M3MlRsSdEoyK/V3pWzVb6VtfaYsT+EtLQSWrhQqV0ILKfLHZxyrYsaB4ISpSD2ItkYEsh/kzmYXJI48a/2XkSpAVIJ5q3OKYPJDzj2IyRzk5X+PayO/sl1oUwzzlWUhHfdyCUaX+KY/qmxBx5EtttXFxHPxskmPnKDgOhiYC81kxOMzm59+4xekyr9i25OECuGg8Mcqi8/y0dUe3YVezuSoTE46VsBnfOAzUJPu+RLAmSZ9PREoyThH9I6LRSjZ7KHqd2bzH3qAi130OMrPsLJ/PosxdrrTLxewhI0pUvximiRWgRlLMoRcIHxMs6P/zf/P7H96595d+9vMvXt/e3FxBvFmt1Wus03f6HIX2+ODuv/7dP2AovFWtNJttLnDieBnEGZYiTU+dyahWFeRz6kxmwVVEzagaDqR/z9TLlcv1teeuXP+ZWXGj8eZbs+L69uc+c/KtH/69v/N/GQ8bl7eufvDBj5C5UhTwAxrdQjqdrm7c+Pov/JdcfP+bv/l39w7ev3XrC5du3CjWqmmOWuWk8ULx9KT9eK+Ry7KjigECmgBZBqWmrW2TDynR8NCCaamCR4kyFwx0hpJ4aXmD0iGxi0oViHsUzLKkYC6j7u39zaKXoZUjlti8ZPLw5z8OlOpVi7B4HBKqDCf5RSzqcbvqXDhaQMuGQpxPwSI5PgKf0+UI5UPRnNlZLzxLYZeMHs/i42Jdyu4SsPdzwEvucejz6bBMPukoeCVJ2X7S90wsMerz0UV20a56OVph8RuzP8QI2ujHP/SHLw9VovgoA5bIpQBN4yX1aHQd1XDzm3S7ndOTVi5fRO2LRQwoCmXkfCG/tlq/snPhIddcj0ZZpomcjMSyGGTIXEkpVQUy6vzROx+8+8Ht569ffO6ZS5Ah14+tVkr37u/fPzhpd8anrd4XP/1K0OsGwxEXVXP1JoNdBneM+xQ+zqQVi7O5hOOpHlwTR51yxg1yueLF6sqnais3i6tXStu31ujc7lx45Wu/UqzXn335pX4/NWof3RlwHeGpGEOnm8LmigQx7+qFm5eeef6bf/DNj9/9Hnv+amtba1ur7MQIJ+SXUUNQrpUO94+3drgztdY4aaZTtXyBnR9WjZY2JDRKMdoP0olVR64G0R7NtDMs1lMdFPcyuUUQT/su5937i4GeoIqnwJ1DZxCqZXN3DBeBnAttVHfO7ZyVkELh/iI07muUJOMZRo9jNiDCqdgYgfgEYcG4VL8G9j/vRdk+WUCfhEoRR4lwMFFSoqKKXWX4s9abC7QUFCZAS2s2GswnfdYppJymvo7mmt0MjN0kRIBkCCECEeczUJUQUP7QqNSPR43T493791565TWsatM5Y5qbYvJco1i4cWXr+z969+Hx0aWVtWwu3+PAbJevBf9A76xuz370wb03PrhLRCThb/4nv1wsTB4++oCZEpraO+sb/cNHvXyGaSIr27QDMAedMhfISLMHljZuWa4m0koHyGmGCP5hPw4jzuV3Sqsv1dZeKNYuZmtrQWklnByUL968+pkXubCmUuWuuNXdE46aYzsloV3hi5BAi1ZZu9c6PD56851vzMaNUnUnW1gfDHVVFQdocPk8rRP99vv7j7gjlYpbrZdPTltrCW75YMFDhebKznV+zKwZE8hVSxWUo9RyUnN6TjKvlmmZ/HA78yg9eqKvs/3/+SZKngXK8/ZFigyM0qFgHLilVn33WQwRLjwAlE3/FszqH3jxg3PGK4aPolreT2i4DcZF4+2f8PmTYRa+LnJnt+gXXgvMlmpZSboen87o44I4TOb/xOupfuRcpGBYAFBXSDucYkNCbs7mGu7Z1Il+Q+QW0DB+JoQUoNougspJbIkTSOBW1s0ax/u9djMHLaIerbFVqA3A41G/hz53oV4tPj5uPGiebJarWXa2jzjR3uSHLsFKpVIj3MbxbMjbvnjpyub262+/s9do6az8GYqjmVpZ+qKI+RkEM7qlLwHRwEQeEK9lSZkjcVQ8SgLov7K/L59n++/FbPlCrrCTr97M1y/lqxthucRMLuhm1i+/tFKl4+qOAzryKhyuZojkWCbBZpZULrt6enz65o/eePTxu9lsqbx6/dLzn8nXqoiJOf0DemENh7ldsVI9OWlV6wV2pKxWy6fNdg3tWRZeNMxQNdLV0eq5c9qIQfo0qKKbuowmBzyiy4g2lwyuqP7UN7FEj+Uhsjzl63ko8jErRUfeLQ1WI+bpaUVmOZ59yA8OKiZfi5G3KziXEzMDx4/M81YMiivGKHOcdN9JxsOE2MfzQWyP4oq/whjjjF2XDC6dC4c4TqG2zC/8zplc0UYBoq8BnbGcCybreX8VsSWVD378xDRsgi1zoKhIWX2MBqXSVnP6ar68kOcZ/0KkGobqcEO4rNk5andPEaesra9b56jNB4jss2iCMmbloKh8er1ewhH5zXGnVa9UWQhTx2V1oCQqOS4pqhgqiRkgp6pdvLjxmZeeJTGcg8jB3ZVyuV4o1PO5So4zE+mLdTMgbEZfp0EcKKT47AZ8SjjbeOHAUmGzuvJ8bf3T1Y1PV9ZfLNSu5Ctb+WItx50bOXYGMx+8gHySQXRvMlld31IC1O37KlFyEvNK7dLW9Z+4/PxP3L790XjQLK1c3bz22vb1Z3IF7hNn/dT6NTVHk5WVGrdkd/tDVk+QfdYrxUajNepz0zjL//hrs4TdTUoBMIrX7i3yi8YMS638SxxNIUOEVuO+nlRGrpSWv6qN5cfZKIel5yzEkse5sCRPBehqxAc6i8nCLoXynOqcVfZRFfpgYjVDpJd5Gt/JYu2akZmF5rWcTGe2N3QmCF7OkW+MPQp67hv7RyEWYR0k6bafbDEBmkUvC25lIVv0CJdlIfo4iwF7d+9jTi6YT7X/nAspOPcvlUaZEXvq1BYSN0YDFCeJB6BrSfRYW2a4p0MINfLUsFFwtOX0Zu0WA659dgIh0ixWSmsb60wjyZlYgsPkWQTksNEMR6gkL22vIcMBaX/EzSdBrVRRQfjGQGkW3cVlMk8wAiXq/YPdi1v11VqF45UGw0ERUYd2SWSKuUwhg5oq+JjxhZygXc5UasWdldIzudw1KRVwSy5XSQVhOV+vVi9VapfLtUul6oUCB17UVjk1MVtkMQDVl3RmvfTsF1/76L29w/1WdS39/GuvwNQqN9/uKGkklXMGPvMX//znfv6rx0d3aAFWLrx48ZlnNy+tsdCh4TgnxlBa6tIS1RrHDKy2e1OOxpE0KJiv1Mpcec8eEduuyyIJg38yB0dSqHToLKM2acow0RjS9sOF0dDO6knFowIiBlUWiXOPp/HIapWqlHuAGM4ALPACFFPsojALH5w1StKzcHWw0duma/jTpRkWBRG8iNuMrleUU5QapdUyZV6EE2icF4d4CTrypalePMvmhasz+ZTEzmCMysG7ySX2NoO3ng/qgEhBnDxc4hRGxhjXeR9yaa1oFNfT0Sv/PqRGXQYEsTBeYoAIHcCTYiRtVYDA4D0d78BgC65MM9yi7eYYGJYBOWUbhiyXihDV0clBrbrC8puk+ZZ8TByWlB5xoFlYW6nsbFZrucxeW1decyUaA18lQXGfTyR1Q42h8NVtNndv36ZT/tStZ/aO//i01cpc3smhfcascjgqutN4If8U99Nwqe1KOqwHmZWTweTRfiuTOOUOjUK6VC5ulyoXSpW1dIVdSeV0oZorlrOFPDt+0zl2VKTCbtA4bl568WqlzKbe1OXrz3OA6nR4qB5JjY5KCL0cro/Y3t5p7D7sHDc3b3z5wjMvFCtFTkpMzTgRgymxzmxTj8wR5ZmA5YpCIdUbS4xczHEl/XxzpXzKKZDd9mq5yNYndYlq3ChermfVYTeaAnCohh0CTPlpZqjqjpjRFRJvV10qufPlZiGiYFa0zkU5iPycWeVriHA2s3nbAECVosdVgjMrVn4iSfGGLLLqYy+54OuCmqeHpFslH5GLDFG05MJnRKEcTgcn1PzbG4MtUeDvfhEIXyJ0gJgxehwy+cfmI2Y2SCYKLonKhA+D71IAb8PXHJ/08Yj9x/mfhTIvF/wsMLYo7ZGHC0lzG7G6BukadWm5kGadJlnDJtTTWOVSZ8mggLEnByLCgL3esMPaPUuKmTyiDvZPJO7fvVMpVnREPJNB+kvqRpsqNLNC2qdjV3KprY3yC7d2Bu89POkPmr1eOsmtTHGxRAmj4NQ8KH0ra6uf+dznr126/Eff/VancY+RJ/sxGH5mM5y0n8v1mQYyBtRuK266zRSuZEtXk5m1ILPO3vij5ofhsKmDULO1UnGLIzW4kbFQXuXMOLbPs9GX0zC4pDibT+XYAPhgcOfOB1/+8k+tV0LuKe2vsMmw3B3uKxH6V8msbV57+bNf+fG3v/P4zt3Vneuvff0vbV/eKGY447AovTb1+tJggCQ5nAr5DExIz12v5hvNLgDFQhop0kql1O31D1ut9XIpn0lyHa+GvJz3Nu7MB8ciynRGm7NEne5Z0IqSsnBXumKgCNh/LckLzsLV05zxD1YDMOAIxcLF44gCyUpyTEC+FHuEQ+FdWEwWiwLwAGvukr5Zdmze4TDAlp7YXPQCBDwK7vA5R6EyJowSaqByXX5cCLksTGBcemILALEZg49lKZgLZA4x4BIihVjyVAtEpkTvwmukCzQQS4W1wCOQJWRQC/mWi5WW4CgEWn04h/6vPxsdcfeSBqEB1JNBf4uDsBFCusX6At1Ijku8tOWXFJyeHDOxqa6soVlmMSLX0CET4NfOvHQyn00Xi9mN7fprn7nGIsh33n7Q5GJ59pNzCUsySZdKL0BAfqoze/PhQsJiuYxGZb3CmS4dZlJwt24OC3sco8SBxBrV0WEEHEK1HeY2guJmunI1XbrIDtv8w2/Ox+9zvVuB00BLq8XyaqmyUSjWk3ZqI90do9A899aYyky1svrM+suVfKqow3fn5UohDxM2lJWo6eQIquzFKzsfv/8BF2j8hV/7tRsv3OKoX/S0SxVuoLAtEtlQQ2DrOSlDtLibQ6piVqsWetxt2iNNXAM+LXFbxSg8affYd1zKZTju1y4f4Ey30XTcC2gjGG2oB1bcy1W2VHveGKUN0CcfIYgfC6ByNRcqOnKIIazUsblgcQTezucMPgEadxlK8/JBHAbPgQ6fO+EXIMdoZljEGyO2KIwilzzlKybkE0dwxrgM+yeaFxis2LBGzwLxkuMC3IMZFK8FtDw8GLzksuzEEhHmc18DXkIsDoah9PItlZiYH821hki9+fSEc2m1Aj/PjOepAbvN2Zqeq3JErumsQXdc98koim6x3+k22QfIAIpdOHZGGrrJ3HnGNUMakEoxcp7hZFv0adrVzKWdOjt93/zgMefnst+XEaZSocyRJksWNvtyXpM61sT48pULv/qXf775P/wrpm8sb4yQx/Bw3TSNhE6FW59ntlO57VRxK13cDsubYbaaz22yhREd6Epls1TZYrtDsbhim4wK6WyBcz0z2WRW51dxUsDs4sZmn0PZJOlFEJxgYbNc2z58rP6N0mXiSHd+vPvg4f3bj/cOP/ezv/Tln/1CuZyedDg/f8QRxjqNhiV21ms04WMcIL12O3o04CqAPLxdynJCcac3UvcXzAvZMJ+utJqd45N2ucS9qJwM0GdVNZnQmVpuEzGDDz1wi6v1qGF1FWd+8hY3nR/7UHYRpSwZfdF6PEt9bYQrLntrBs1VtUAANy6WOcIcRymIyBFvs0ZRmBcuki8JDf8amupRV2hpVBCHI0qzt3lXAYkJowwpcPzgGEHHbt5wzv2c9Sw0nkK/DLNsxifq4DykBXdBzgI6LD6t51L3BGSEJUKqYNQ25SF9KW66nnC2UptFBCR3jEjHHACVW82yZxzKhamkC815ZKxgoFvW77Q6yErol5jMaNQKY7J0Z6voGr/SqrPjIM3JS0hZSvPpJhTIzUpb61u/9Y0/msxGSATZI8RATl2hnihRnDGh0S31NavW6WXXn79+odOe2IGckDxclGGOmpzlx6niPFtO51fD4gqDywChaanIwTJMZ1kYLBXrxep2vryRRojJtkU2KdAv55I0JpksJ3yi7TpfLeeOAkSXAXu4OBUxU0hXty4F72llj/kt53y/+pmvVFY39nYfTKfdG59+sVxhJDtj//OEXplNS6j3IUxh0GnjUubTatBswac/RPuPpZo5A3K6ba75Zu2DqSyinJVaoXHaOjrYKyN56B3PuY+VU/h1D4xuGVDVizYgXJAZXVMw0DDOnspVWDb/l+H84wpTFWslaqiMzgznAnpR6JHbsosowsLL0TyM/TA5zPZR+iJvnzbvi7P9IrbUaB2zI7UovuhrvCp4F1jUKPPTjrfAy8cgYMDsiYNGGPWNPJfcYqcIx5LfchCPT1BPGA2HQ+TTeQ5JbHVA3roUoxk93iVsVoBMbGiPE5wYD4s12JaXyq3Q+XDsBfvuqRG1ajOu+mSHL7vG2eXLKt2skCtwezXTIo4oEogYig5VvaqUkOFa1LpzQZWTO4OgVi0PJtOL1y6P5rN/963XJXPMZtmwSyeMkEK9MQ8vFtkRDnHoA4SmPefDKnC6CgmpP4NSGgRmKkFqmplyLiMX3HLLNEfHc6gh1/shdinVugn2FXGeRrmA7mmB2R49ORzIXnzdJ6gDr+m7pIVOOxF0G9293eDCdjVVkLgonefkTzozNKtzV26+8sLnfmJzZ/3/9X/93df+3K++8oXPsODHmgQBs4iTaZQ0ZWbWpIUF0q8M0OZzp6puVdJ4G54Sk2qtJ4Mwpj0YIiVm+z1nAqSmg+Hh/QAdiemQfZKoH5EnEZlQGQlR4JTHEt0tE54rKt7uMYLwZhXhggLMZKUqpAt3JdbbHdkrKrkY7AJOqBys+ZlRIM5NJoXS27mYbfHyAHgriSDjTxm0OMWW1tZ4eBe3J4I/YTjqoowjcRF/UgoiMO+vBH0CqE9rlBFS5zBHGJTJxeMs7m0V5pMRYVlARgiXXITKw5urykT9Sy1T3Blm10Y9bf2hK2IVAEVLksz+t3mg2wPhQHTOzDc96HUrlRXMwgY1M+GziZBKGXKELMWOyGq4eoGrZdh+kcyMxxmddjFYr6WYFzUH45effebB7v7j4xN1bNrhAD79UzEIOYYs9XOVYQfx/qCUX0nqwEVoFGWmqQ6RSNDL5sN8NUE62eXBqdxwajEo17cPdf4om7FqHOXk+0CWLLMcpZNkMstZFsxXOQKYnpu56eFHH3fauZvP/jS3yb/7+tsnDz+UqDMxu3rjhc9+/kt3Pnrnre83KrXVFz/35SrblrMMhKVz5rThyDiDTC2UqL1Q3ZJ8KiTHqd3IkWBndAY0vEQmqmEqg3OOXZ0MOK6bsS7n2BT73b1xv8MFkapwfp5ErVhVDlbHhtfI1+hH+Py/GYxWcLGSw8NMLqB30kcPX3N3Nt6RzQF4HN438nNWbyNBHkoOZrGPkYVcqH37WdrxV9PpARfQntMV3rdcMVcKyCWTErQs+eT4T5zSOHnOZZGas/BnbHEYucbhYsMZWLO4TCovkUlZtGzhoB9VZhwocMGA1j5L+OW69DjvOFLVO77wDCsJxdV0cT2Vq3PkdjqTZ3u3rbxzOAunFiJtZ+Zjx3xyZTbKkty8ziK1iWeYbrHRnjfrXuI/CFLLXaRVXSKCVghaLM3OhUyIejd6JK+8dKNaKkCOuwf7G2u1HAM7pQQ+9mk9Ojl9+HCXnU2Pd/fefOON/d097ZaajBAc+Swz50wX7Cq/Gds8pDpGnyth7Hx9czuVzJNKThZmqztdIONQljaYS8J7xESXSafDPmXuBecwj8s725/73CsXNotBZ/z6N77ZPX5sq6RBr9P/wXf/w+Vrtx4/2v/CX/wbP/mzX2W/kTJIWfBjvZTOkF6O3BpHyoGsm04MERBRhsVSjUjR4KPRwI/Was6ImKSyzDpqHc6GJ0x9SbhqDLmQmi23KOvr3IYCtgvDZZvyYcgQjxOj4qLNsgIXybvHKjaqZxzdz3suPhbOW11Qj0AfFzxC4knMofLoxDAaAMVBHf9YUyKsMXqjWnPxGYsSxPiJ1pofBhoxxj0EpgyJVQaB2aNv9C8H8/dvA/D+TzNHbi5MZBM+5c2xQOwqp+ixAFEKXFKwxQ4R2JnvWQDDtozyDOwZi3iRARNDKC5SX8sUtthazkLFZNSdjrjic8QcjHuLOHuCFTMYDSkES4WoeElBlIv9uKNCcz8dgy2FFU2ndc2YJUeiTxgRhpROCsv3cEA2eP7FS7/6yz+1hXZlo8WphBAhp0JJ74Qi0Vp/6qMPb//gh29vX7y2uX7p/r3do+N2qVDmbhgNdFVs0gaFoYRL9+lOSVzIxbeayCU4BRjVcSQtXH/E8fL5vBYltC5Pm8GtFAg04RsKn04sTX1PX7h54+LOWmo8s+Elu444y40193V60Wq1zOkblQvbX/7ZX66WkwhFFcoGoowcNWRmbCo1B2u0SRdfN/5UKwSLwqDiTDGn6zqJV2eHTDLzYThuz/qHk1FLIqdChTGwCk2lBohErRGzUYm+akXUJO5sRbuqxM0/FJADcE7u/QmEoAI3L1VrhOApX0MSA2DQT0Ec9jhmMxC9EukR+lCuwyOUCyz+VINl0RO5KMRmoWJpY1l9rBQdA1uuQY+zGSP7UxL7SU5n0mrJcC56+1RaUJfCJRcHQGa9j3lF/ng6NNFXmdLj33xiuzOY76ISyQ+lQInRRqO6RsfCTbHJJAd+MRrk3rxOkJzkdTdogeDczIK0hgUCWI/r5tnjx1yLeRhiPcZ5dpwRByW62NUfQFJq11GT1LiPsoMP1WVyymkyGF67XvuVv/hFbqveOzrJFXKvvvQiN6iJhm0MctrgPF+W5bNXb1199dXPlfP5eklHiaoGqDy1F4wZtVlDC5LoGLA+GAx1a2AwL5VJcAWy5yBf3YiL5lwOLtRFEViQQWqGRgI1VFabkeKqXIlGZukCx+xz9UWLwljdusJYd+/+vY/ff/trf+VvvPLKRQS9eVYmFNamb0oCccCHWqOnEGFLdpWgzM34E0an89eQVOUb/Ui52DYRsoQzbCUnrQSFzOIEbYkES0WxOPSr2lZjc4bajBCsEgGJ6tXB6M2/EYnRQ0wUKi0HGzlFPCB3H4O8vPdZpnKO3iviExFi9ChnkdmhgDFJOKN1FkFV92pU7I/i1td+YlL+lDj+LaPmJNZV1jwYmLmlxLjVIsHLp8XFGdsXaTAPi+d8wrz9DAJBC5hsyD3GJ9dzOB2goY9eZ1GRD18pFJDLh0PpwWmel5NPYBeDqyENKwkE8TOw0mnQHPGS0kYjSIO+A0kiszVohdRqpzhKnTk6tlJhTRt9FQPHopFo/aPjzHUSKFMqXxqyUQkKx79EL5zqAN9I+WZWLhVefvX65Y01Ljz73T/6AJUcLpm4uL1+3GgSMeCoVD58dMCeejq6G89ce/XRPkqarQ6HrpE0dT2Sm7DjA05nsMchEbOBbQSaZIIpJzrlsivFQjZfqnMYB2NBMiJJpvEeLERpqEwY+EyYHHLwto2458HBKcfQHLCFAu1sknrp8tXZ9EJ2Zf3TX/gJjvOvFVhJJLvohZNr+jW103Lgg9nOl4qJknGUVQY9s6o4didqLYdMkTr354wyhj0xL20ZwisU/JR30iaa0CsmeOw8sBoGmQER/drDRwlafiykkmZwyz6YQRzRmLB5LOeAzKpIDLlgXByLmORj6fEhlVbijSDlt8CNs3z8x0wyL7DJ4iCsqJRC4aNNWsrcIrhQGXqF4VlG5C3LwM7Jw7oQi7RY2DMIBOGCnw3iQ37SR0kiHFXosT01NH5nIhMRKa8u35gRxTAQDULuAy3TkiNpmY1Pp6OD6aiBcAYqVndnit1sw0fqyfCVIR2jLSgOHmOqxcWC2uVksnkdh8GNCvCMGkSN9sWN6hyk2xaMR7lS8L/+ta//9V/6aUaih0fHqKrR47EqT6JI1us/fOvgoIkuF7tjn33uGbWsksCCChZkYMxOfbTEucwGxz5XuhAZ0v9UalIohhVWJgp1lhPpsxGjQC5EKo1veiJxh0QpqK1JcMJpHgwbWeacBh+/8d7tN74Daw6H/dbJ4ac+/Rz7Aj//ta9fubqhlkhEIQ7RT8eFSkmP0SZtjVoxK1leGlMAqRGBatKcozrFwcYYTG45MJ/ulx6D3jldKCU4QUvh9IDPqY9iF2UqYv8zQhUYDqDWO4pBRrkrEAk0L0cVCxCHxoeIacWFtNA+lHdxtoV3bHJ4jM1iGKVK/3rkb6mTQYTpHe3rrMag5iUY1YoFU2BcXRZd1+CRRmgcKofSx3jGEqEhmA8ZffzXJSJ+ixyiMLHjkiFCvuT0JxjjOGSILecCOJT+rXwZSarA6AiZrpXCwkqmxI1oZe7pmw6PpqNH88lpMNdJTbAfjAd5UcM2N6OdUq9lUz4QIf3jrk/EJFrvgiMcExofijKZPME/4kP9NB8o5MJqKfFzP3nz+VuXS4Vcs9VcX6lt11eksjpPPto7uP/wwI4InDHlo/uVGEbXdNMQ0CFL3MrVRqPePhxYQCqKoIiFvmDMpbncxa2ACFj7XUQgJJg2haVLemfM9EWwHSYSjJXcj9vDd77/zu//i3/Qbd0nQyyClgqlo9NOefPKS5//PImvsAJCJmgGxCLiPQlvdEyoxhBErfN93eDLit5ojFLSXEf55dEYw8bopIybLtBWm1CqaOJxNS/H+SAkAgQUZ6uO5Ln6lLsl9lyNPs2qCK2cHS4lBuSymCuW6PEmBZDf2WcBtuxuYIbrXFIXQOa7sDqTHD1GMmkg1vqbW9QCxQmRgaYzTpRLnHvHmEW+BqEc6t9nwpDHUJHrskPkZuEgNkvJOYA/s1VI4uKzVJwN6nPtsn/OX5UelwvDKVYjMsUwXwtzNQ4dYlFrNmnPxifzaU/KK5z8a1MxdQkwos3xUDNllqjCgiBhEbQ6C3nGZyxWa0YmLrFpk2ZOOrTXWE9cIAkq/Y+GG8H2TvHX/vqfK2fDlXp1e2vlleevrVUq0DbbJn7vD79zeNJCV5zOTPpt0Dz9LDIWXTZIvzpKTPv0hrrvFxqj05Xiji6TwMxtvOi6sszBYBqBIyeqYmWWKpVYyT0YVbLlCs6mWZgNTpv/6P/+f377O/92ZWXj+vWXtzeufvozn+eQj6/+4s+XVsqKmH4P5Eq0dsCnYT94UsxnfEisbhRCeVspqxmJxJwUFdralBg/Vlmkmjvus0AvpTWmjjQfXMJKQVkD4arPkZavWhAKp+E9W7vnbEaKFrEPvwizoFKFWaCKEPNdOHq0zu2s8xkbNXn+8WG8h2yOylykclafpz5UQwVL5gJWyGygYomRO4UiCqXoPCF7YEG6Z4l7YlqP/ACW21IEWM3mvx4ggjdwDx1hX/g9zRSlyyM6i8+lx+Fz5nMoKAncyaJgVBZq1ZguIZiht0E5huVwymqM8ICecD5pB2ENpVCOYyMUzMhQkFUKTdDAQ1cAD4sPg1wxT7PCfNAyZPMuugweonFulL8W8elZiRpZBvwxe/6Zyi999XO/8Xvf2tjY3nu8v75aLRULp60mI9LWSfPa9vqXPvUiyVMwrTGgZgJTsLsDoh6ypx05CPWEyCefD3VTZxAUszVaEJPcojSnS2GKlVy+wtqkBsdwFz0Y64yJ9GR1O99oTPYe7vearVq5tnPlxqWr19fXtqep2aVPvbpz7Rrp1HiaHxml50L6Qguh4oxqwBiQzIjQLZfKmXuM9eTI7iX2e0ymAbKlfnvaZ5jdVelR4OJAu1CHUBSRkSgmHtWLqkh1I5QxWo/df1w4xQKIwC1UBO/CWgV7NArm0H0CQgH8qY9DpopVMqNHhaTEWyosYeZnRYWJ8sCbjw/tjAbihgCWZSFzACzrCDlSHj2CO8/4uAnjf+RjMVoYF15vxbhw/7MideFB5Q1UwNmgZ20uGp8JxWjJhzzo9JnR2cxD8jxIjLv5NMzS8CkxSs7aiVlvNm0lxyh60RdkkeCwS8CVJGyheZDYQ5mCSdRPisd4aQCnWZCsavk0NpOrOIiE6OhDVhs00wr+3Feeffj4wevv3L24s/N473hrfaXx5ilK3G+/+xE4PvvCZISiydbmsDEYNtugRbGVXR28kNVmclzci8w1LGZT7OxnTMypqYN+juVDtG5IGArnHKvaPeRk0G6hwHGq+X6zy6H1Gy+uXX6xcu9O/+7tzurGWq/TeObGp648f2U0H5/2Zy994YvKGZ2lJV/FJCakbJR1YxHlSPm2ZkZDc2XXcq/sWplYxbq5rO4EH3YmvaNx73A6bJIBLhnW7QNOoqrAhkylZsQnFyHVONeQYz73OGfeLqQSpiowKDnp0VcuEQ5Xc5FNEAJxYRZfTA5B5GGAEeRZR+fFG+fo56jRw/lPFI2shjyKwZKEkxuoErHKTz2hfcDp0+KsT74dXIQNf4w+zgjYu5zzcEGit+MOBeTfOUbB/8TvnwnUoXTICSCuj+PALH4wBz4aV2rexU/yUprrSXMyeKRUscaMLDJkVElw2NUt7BFGXSH4gNUZ3HbAu0ghavXU9UXRQbRKDC/4dQY5ix3xDvOpv/pLX+LQ7XsHj65dvXJ00NzcqN+6df2jOw8ajcbvfud711/5zK/+F/+reqH2zX/x+x99+9uonharF7OV9fkMJc5KgQMWkXDoXjVGvYlaZfOwdTgZ9rEy+TrtPD7oN8bdUy7xTW5fyIb15LjLBov1LVSw0ZwZtvrHF7Y3V1c2rz3zXCoxfuudH3/tV39tZaVkQh0VjkbWmgrCkDYFjKrYFZsrTVjUswCsqVbIsgkXS3yMxBmhKN3hkHmqVl9Z85xNGFIkdcC5tmuqAMXSC0JwlLfoaKymQKt6/ITHB44ggXLAju+eCKQozdHeHq8z682/qlov72fA5uST5d0ZXqpOBUetOw4SmZi3hzF/5+aSaXgEg6NRiGdFmhyLl404cXznDITwaVv6Opg4VhUlUQvUwPVeFKYD9v7msUjUOT9n/cR3jOMTIWIPXxJWLsvRKX3Ks9wEA6VpNGnNPrTDpK9/xCpEMM8lE/VUqo6WqNQ60BJhoqeeE6oks3CSZVDMJRd0TDWedbHiAyEjXKQPFKD8icD1FFilMzGbrq5V/8ov/vR/+9/9xu27t29cu4VrPh/cuHrhwe7B3umg3GpVVis3r1zKVv5au50Zt4KNreeK5cp4PB1pbxRjZKmpqJeazmq1ens/i8p1r31y/PgRUp8ia/Z4jDhGtJ+ZrUoLYKce5rQfcv+w8/47726t1asb21x98eFbP3rui194/qUXdAaTWEPJpJFhSYYsa9ytSlWx8WDRUMLs5i6OVeEpy/xr7sPklU6PA5bno/5s0JkMuqyCqMFKF9nzEWToDNFZFWJXWh6150YfDY5WO+7jHO39xIscARmxV4RMYB5HhMK8noJ3ARfhdumKbPqeT4tVLzGoEeFHGmS06o+iIYylTEEtgR6N0MXlKQthCWoGa8rMXx5nHqHlf/knf4vCAD2AmVVn/lmYvINiOvssQJSNs37LNry878LknKJUuLrwQIbWm0Gj8hHjyCisLlZC4ibVGSdQQaOfaRPJYI0PeXpzNm5N9eNoenYq2RxJpaUVeciMB2Qar8FflgiLQpkwDiQOPClVUSj+tJxSWMLZcCE8oQm9fnPrl3/h8/nJ4IP332NDA7s6ENVc2FpjjDzsDtunLe4R3NzMfennv7J+4Ua1XF+pVTc2Vy5c2tjaWqmvlNjQkeGmaq6vyHLnaabfPzk6fjc1P12vZMJRa3R6ND7cSx4+LM97q7VSvlxlusf9E/fu7h3cuV2qVdlg+6PvfKu6ufaTX/96uYI81kqG3g/pkjhQ41JRm0pN1UPWxZ+a85q72hYZPNNiVFujvMKB8N603xp1j0ftg1GvwSZMbmVFBsaCEJryaqB8jatEVECuupbJQEWp2PXEBme1JMWhfKEvkd7ZMIrCuwjPWbyqSarCakZA57Ccidhhscg9PguhUGZ30QClKCLg2NOB+KSYKyCW9ih9Xjoq58gpwkLB+PDLn6e4Ea8lRq+lNLhQVl8G4BPskS3jeTofigTsORswRhsZLOvO4qN3AVwMesMeOPFzAwhbCyO30A5L4MxVoC/llZsaZsPH0+EjthomE32uTeCkaIZZU50+yFIAmBwZKqgQOpc4HfIHDTznk2TCQyAlL4Vm3YYLiJXtdp/5zKt/6ee+lJ6PH9x9xIUwkO/GRpVrfzl8/ujhA2QxkO8zL61tXV2BK9gdX9B2eX6op7FjnoULuixW8LjorNtv73NRBnuQe52j4/0HhVnqyuqFy2ucIVxJlivTLJfdcFxx4v7Dj9DjZFn/w3feL29s/9TP/+XtyxeYD0vqKtU4tO3o8pUD/hwfqmPUTicpx5jI1NoWV64+74I26alEQYxFE5PufHSanDQSs05yPkIciuo59ySmOGFZDZMF9uQQEZgvrohIHH5VmyvZs++zjtjOOkTAC9eYjCIvvsbHC6qLgBcuxgvKm6tk30bEKROqKJBHG2VsGcabl508tEdgFKRyOfMsw6vA7H8JwufIMmHpO58Uwiw5LaMDCz7LvyW8Zz0c0LK3x6mPDZRiP6UxtsThzElplbeVoAPCid4MCucLd2mFPTABqbGLPCfNxKzJfCmVQIuae625wpqDgDUGVYMf4bX862Wx2yCLqDTe5A0QpQpJQcNOy1QTS1vux4qKkjYvVcr151988bOvvZhPjx7df/Tuux/AX/VCpXvUeHzn416nR/BsdnbtxfVkCsn/mC147I3gmdKp6ZbqZHI8lV7YdJBlDJ3Ocunvw917rKRc3Ll18dKtte3LYbE8K+RZc2Da2z4dPnjvw0qtPKSnmo9/+pd+8frzz3ItEn0BqZSIShwoiaj9ok7dsgfr6AckU2TypjKlOGxMIM0tGii2fE25nVt6rXOGo83p4CQhZUAW52vpXDVg80Q6x3D2zFDUtd1GhypXq2K9rFjNOa7YZYPVgTlYiMjkLQs3PIRkySEOaTmQvwLr5aKUDWPkK1fnuXAyEFlVBjE+4TBcFsRjkIO4ml+M0Qc3inRme0cHPTnQJY84IAbhO/Msoo+c5eJdLcACQpj1v2hXojBxkCfwC+K8o3A4Z4fb2V1lWZQ+rwoXh8U7TglUY00dX5awetNRz+gomHH22lhjUohGwpVpZ9Tfh+1S6TWYKOA2TEQ46iOoEzgYFjaWtZjFFlAxLK23WNAiF6FatjUwkKTDRl4UAfWKoGIihZvU5cubP/MzXxqNuu+9c6856B2dnFRL5ePTvQ/effP44GH24nXkkGs72Ucr2XF/mC9lhJR9HtzbTUxDxn70rBPOkqCn4zTjYW8Ypsob1Uv1+kaBi9CSI64/SiJIZWNfEDx+0Gqdnr78wnMnR48vPXNz/cpOfa3K2FAdoPEVzRFMRhSqJ7UjlgG9jEDlgoeyo8JVRjgsmYmg5LcBMiqmf+Mu3WBi1JkN21NukkN5AEWeXEkaEcijpLOjFl8orHqEStPsqL5UvDg5Txn+ox8Fj7D4wIYxQmQxnXExn9iF+DHzs1y6lDmUOMVQuBiIvZQZ+fAveEZaar/MydOCcOkxcL74CsjcvHvCmFBoDMzidzZzWrjLFIU8D7AAdfBK2ZKbzM5uVbzwwrSwLAX404wEipKyAHUuZ/A5OHEeJntpqRlR4Kg3GTenk7ZOuUZdm6ty6RRp6eFDhAvDk/Ho/fR4kinBfvkUO3VVrEhgaPYhGnWloFRe1OFFIgxNGFUHLm3uA4zJbyBwit3VMbulkKux/D9KpvNXLq195StfTKXyH3744PHDhzduPje5e++9t956+PG7pcpKuVrlvNz1i+X7bzW5CBtlAZoPsSLtAHdecCzAqKNTqFjwnGfW8pvlcm2tuJ7OVE56k6PB4eraTsghMywuJhJ3792/fHm7WMh9/OGPP/PCqzefuabTnxgJMMqUxoaKh3RFXEHylUE5RkWNp2vQBKWBPfAGIs6k5RpM+6cz+sDO4bB3xDI9I9wwXUIFl5UJiWTE5a4pUlG7R8RqeBfViEmRf+KzlCQHF+FyIVzChcL5uneEzWwKoIgjL58AuRiD2MtCLJJmxKNwkbt9sUZIMLjyci7Owzz1MqbEjQh4hJZHMxo85XSuJ3RIDVbwT3t8wPNezlnhFYn+FxAyLteg93LRLMEtQjg0FuyM45IljsKjIQJnimCMfuKMOlcyjybHqIPcRdLzaW824wYluj0VlIao+E/7WjCfHCemq2GwhYwUBUib1EFG0CxTJC07WD6lJmr9BjVHdDAJCITJUqL04G9JIHpnUzeGMwL9ZGqcK2Wfe+F6rzt49IBli9PRpLWzvbn38PDtN964fOVZNEZZkGCz0d3EvHPSKuQLTN9m4XjYGdDPpFgY53TTabKQKdbL6yurW7lCMZ/O9hPj7737xu7Jx7/4wlXb4Zdsd2f3Hzy4emWrdXiXvQ1Xrz1Tpf8MJe20sYGYUNUl8lsM9i3dVoYLsrRCNN5UEBW4as8twHCm2ozV+f7xtM/RNENt6JKWjOQxDCUMuS8XVyDG9BFhGmJDFZmWvorDnqX6dW5LDh4iKmxX2j4cn9hdpkUwobF6MT4xcJcjBYniXYAbLIVkgB4aKPtZxcuIL0VEKyULj6dDM+sVuTtfBdAuCge8jNnM9nKQMQIHq4BLTmYUBaulE1FGwX1gB25+Lhz+lhPzN8t5bE+3O0QLP5cIHwuFBmdECVMVC7O9fQYBpKRQD+OAGdSpRqgXIzzQRl71cuJBPehqsT6RmreT8854eKrjzlLFZIoDPrOo2nB6haZHVswmXYPrVF1RIgyFcsePf6sLDfQsqZYoD89nyGo52iWp68/e/Mmfnnz7Oz+kv7py5YWjo5MHdz46ffQoyclTWzvsjaXb/PH3/jgVjF/54lfzxTz6rhy9j0zlqMHVvysXNy+vctj2ajXIhf12/8HxXupa9dLFz+bW1mg06An3H3UOHu3e2Cn2RuNnPv0T15+9TiPCJNUYiQRb2pRY5SLKiApPLmRNXrQ6Kkvni5/KSn60VugbjTi6QvuVkMpMOOURdTlaJNZbGYWiKINIRmpAhHBx+dIS5S07mv8TrxhoyYeI3eM9DaGojvTJS/4OxAE4swVRmpeshMGmdlNp0d+yr4vEoTTW9flXFC4yQ+UCRwH1NWfhNQyLhs17uFzH2OeoGEaUakXigzuAhcVMjqJ9ug3ap8aglTvLBT4+FgfqS1pAPi6wRW2oBfVwS4Ae45NJMg//MjQWVq9FYuUdJSGCB4JH9ATtIVSEeLRxCfU0bQSEnRBIalokzUzcprMx14k+BijIrIW5i9pLgcwiCT3hizgfdW7h4mcuvDB7GygoCPPgpZy6isGCVAbypbkw4ZAk9kg8uVPllVdePDw43UOFtHX67DPXe6ed7337uz/38yudZmE+X125lOm0Nv7ou9/Y+7dHn//8V7a2tvJsv0oGm+313KS3dfkaZ0L1JsNGr73b2994ZfWrP/mVQTfBBcDIctBGP3i8H84GjdaUA7KvX95crXG8jA5tUmNiZajUW0aw+WJ0xUmDLrIka5ZBB09OWTM0J/KR5JpUlnOGp/NRIzFuJ9FipRhSDINptnLcvKgTJa04KBUVEb5miHjaYnbYFEuUACVJZt5PPD4d5h7Xu6FYCiW7VYgzLCFexqfU+JBLzooA1zgFpBHACJFLlg0uHdNaJygjEGTLPWaLLEu4FVqlEBU4XrZYL+xPZheU53AQdsnFhbKgvDwOjzxy9fmQ1QAsdrP5/jIG9CAx/gX22OkMLOkVefP4rw+gUjVA7BFlqdYZKPLCxByKq3k5MyJNO53ScTIIWlglRLeMW1cIRAehk1/aicn9FPL+dDGV5MQ+aIszgsFAA24zRCtzJpIaeShKS42ozcxCBFYixmr1pZqiSPWPiphZNCjlQKlkerq6VX3xpatvvvnOw7v3f+qnvnja7Hzjm3/4ta//zPh4f5YNbr628twXvrj20sXf+6f/5s7j9/b27q6v1je2NtqD49PRcS690ewf5TYzV567Fnbq9bXCSi3bySYaw9E0SB8eTn70w7erNY5jHLYa7cZpezxiu4Y0DZTGpVYao2c3ozcrTzGbG18oGyoe/1YpSC0c/hrPJx3jQPRvmWzTH7LkyDywoB+FrJsnYHkfVHVhOES0FDUeqpUIr4pt8SzMsUmIXEIWYM4kFoi8YggXl5DGTmfCebyOUgTkHAzYJ1R4Vbn8LVIhx/hnGK2SfUrcx3Kq4YYLbR+XP+GxKFQB0sn3Afz3iY+lQK5xcn1wDxmny+zCbNWGs3z4jxDEgJGDR+A+zlfB7YkLJXaJfPSNUUWh+PoCAl5B+I9QWBrwFazGg6yKQRnZaia3MuqxdQD5IH2jOANJOw/jNxs+UuYDluE0kEIcwqHR2g6gUwMZ4YmfhZK+xBEQ8RkKRSB5qQSNILARrlJGneFjwTDxi4ZnCd2erXoItrdrN29dvv3o9U67xYktDx5+9K3vfedLn/+Z+3c/zOdeXNvIX7629gt/86+t1Urv/PBD5EfVl9emV4rJw9LN1569u3ty6Wr1hf8faX8Wa9uy5vlBs1/dXmt3Z5/u3nP7W9lnZWZVZZWrssqNypiisWQQkgVYwjwhJIMEyPAACIF4AIEfENjmDSFACAuQjRAyVVQ6y1lVWdnn7e+5955zT7/P2Xuvvfo115odv9//izHn3OecW2XLY801RsQXXxdfxDciRkSMGF+8/723z9l0kJ27H3+8+P0/evfJ2QfnT84//tF3fukXv3h05+jZkyP2UD07vzo8PNTjylZm2ukH7xkGzRvWSt1rYcEcAeF7Bp1pYVpiNmCFw5xPFJ863EUPn40iNRGjoex9en80YT8LXl/S/FUmEiPDaKQl+sJpS61CNjWit/R9geJzIo1SXv53jLZ4fw6RIGuvJffp5M4jA08ZaiYPsD1tDjOHVGTlFJSgdZikNILikbcoiknM9AI3K+06CyVGekFrkYSEvXhEmiqIp0omfxptw2NNXCo0EkU3om3UT7MJTkq0sEj/GeUrmFTfohjt3llOjhbje8PJXd6Z4O1v3xfU/LJjbpozxdFf8J7e2fJ2Hy9hoo/HQ1baOOTPQKodOptF+6bY0o2ecD/SXH1pfaYFYdiEzFNhsYBG4F8S5BDgsFvqtDxOzjZwu7xRv+9GUmzaO/r6F974f/w7/8/p2fy9n3zw7Pg//bf+pb/VH9584Sv3vvBwf7z3y9P58qtf2XvtdnV88fo3Xh3t32UzKJ5e3Vft9nb5o/eu/n9/93t/5//z22eXH9/d3390NOcT3K8+vLe/f3Ty5MnF+XSxOkIlq2dnLTPO/3atqXIDIUdw9UmoOLHdRn8+G9wyDHTFKrn59Ow269TsVWAeNrvavTvaves2ds7uUJb+pV5wKaZdnazqZOnE/qry6aP0+tyUUlPSRg8tiPVTUFXGcLQYtlgTLk0KRqJHaLewOiOp/vowGEt0LLq0MNQNG+viKDa/FHvHoyh0ETfW4kiF12cqpWg8p6Ra9gpJ9E5AwtJ8Ki9hWKxKKRHVoAjqTGQtbQOXWQNvUl+g2+B+KvRZhkIabTH1VQdmx/b6Y5YyMtxyZ8C+usvT5fDaqsL4CTKpRriYeVotb5+z3ou5ctyF1xj4xCwrP1j64oB7ZtbAd397emDkhcrnknB6rTGcF7tyUSHV18dBHTKGsPLogvgcrTDvKA1HfL0TdNZnMxLzxdcfXv7o/P/1f/+/nl9c/vD9n+7d3eNbil/4+lfv7nzF9z7Q1PcH2WB/+ex49snjy4+fnv3hYvrDb3/wwzd/8uRmOT566ef/8l//8Xf/+Orpj8/P5+//5PTm/Oz2ZnZwny3GB4wHMWyCTjEZeqaCVUR7qZuam4VKMkoKv9jGht63Q5Y3/fmULzPNple3U5rEWxHYv39yONo5ZHCX4RnXzEoX28psHVtXushR3FYgMcm2DqyNHqVZB26KrqNVzh1WcBuBdm/CGw/Yr5l1Adw9wU18g9MJSV6aC25StygsaJG77j2xSq0METYgKOCuO1rgz2olciuSZCI3NWA5Nujmr+5WLQmpFFM8IHI2yS3a5Dd0Lkpqalcw8f8op3BUgXUgFac4NuX1BJMd0cy78Lu9Grhj5HPF/vA3fIoCP1zyAi3v4eGnPgDSbDGUesymSv3eg0Hvhrdj/cEFbvxlfBE/gq/TFlQ/fBDnxLfQxSEYJy2QCyt/aGi3F02E647RWV9k07d575pVl7fXJ2cnDx+95BczfGGYPaMmj9/+wb/1P//XqfV3Hrz8la/+3N7eEb1oqvrsdv7s+TPa6Mur+ZPLvfnhVw8e3Dt6+aVvfuWN0fjg23/v937yZ78zXt3csrXUg/2zs+ury5O/8M/81oNHd8kpwjXF+iabstTw6ukv9jQImhH+M5ZDW2fAd4hnjGzNb89vrs7ZnXVxe8PztVvhsAU/a+V4iZ7PMzoPGda2/xpBTulfwaQMIijyiP+TjrB6EakUCyxWJ5Sa2ng2oZpfzE6GUdE2IhMm2iCdJKNVUIUQOIrLy6OrVxULBBYd5y0BHcOGGL4Jr3fg1joWyYbVBjfyPptSCB3rtVIdh1RT1VFfuROM+deYn2GJEiTqNdQ3+fws1Bi5CWpcYJ0cCDUzIV6nBboGem/2pXh2ptjnk+ys2FzMp8PBBS0MX3tZsHoGJ8xrN3Li5djldZ9XDWfnvT7uuuPutrweaNZ0M7uVuIv+wsvo9Cw4gKMPWc6DIhGLykLXOyH0F1eMI4YTEyGD3/j1X/nhm+/zbUOeE5dX57ynO72+PDu/3Ns/pLfMDOPVJ+//8OkHfDWK1hqBE97TG+4sB5Pl7mtHf+6v/MI/9U/v3xu+/9Hj7/zRt3/47b9//dM/3plej+9/cTbfZRXc/TtHX/3aN1/+0teyDxWqax1UL5OjKwFOKtOOjfk0QyyaRIK8+XzTp688Pbm5PL6+eIai7ChDT4LbELu5uiKX3b+ZzrHhNO+e4I48BWgEj5zXpdzE/uxLEX0mHe5NL1fZ55DnBltzN6rClcBC4E9MMt1OG0QhGx5i1sFV0txSkk6E21SS11Ia7guXiBItWASUnINtnusIJJwK3sBcpFnjl/lScmuMLV3XsAQ27GRTeQrM8IZlQ+uUrKQCdkgbES/C4VsoLyrZqRHjQmGLHBacLCWdnAc3Ns69s5rcW07PWHuMC7E3hINVvulQAxXWUde4uRrrmH2xR3x+iTdo+SrKgI4WbZoPkilJPIheJO0L+GQEf+EBLeKTcxBRI9ZHBatg2pPqm5Yj4verr/7il3/jN//8n/7J905Pnt5Mmcmcsb8TL/W+/f5PGT5iuwu/CszcBM5tX3LF9uDsRswa7MvhzfnkD9968/eePfvk4tn7q+uPB/OLyfBg9+gNtvdnfez+vZf//F/7rb/6W7955w5d6uSrTJH1dtqwmR1LRc3YVXh3tAqbUgKF3vDi5op3F2+vTm9vrlfzWzKPe/OmJbrRTPMWL7tUYQoIU0dlFuNoka7cuuIrKaBU+W6ENoQO8DnXVrSmhPgFDkQoQ4uFZDWoSN0LMWFJNMcVkkv4iMuxDS1I7BB+UHm35WyoCF4QvsUocE4laMM6jDJFYcpWagl74Uwqh3IQtjZpgNunF5l0sSJupLGGNF3qFn0hduglh+QtK3fIWzgFimpNw4ZERU8IcrNK7YniwPA/d8Bl6JyHQ/b/QgKjmTjhgI0tiOBB1kSHPgnTb7w9p+kZ7O3wAQs6qAyW5imRnfO58zrJ6HZMDLqaKZuolIrhmBgwCmhg0mRH8xgAwdQCO7L6si8M9f+5v/nP/NIv/dof/f4/+uM/+uPnzy+nPHZR3WmfF3wo+JZxotY9tGrBOA+mPJ8xOPTTP9Uqfq2NraLYzOreztEX9u69un/vpd/4q3/xn/3P/PVHL91lJHbnIRMzknLUjaHuULCjbYytkg/Uj86FWfa2riofYa7+Y4dI1ojOblj54DINd8HBC7lt+GMXrF1uWOxsEZ6wkVpDy6OduBdqlxePNSA5bGmhJbxODNySC0SnapDty3a4cROtuR6hPC8B2kqUpqLC9a92EJIwP8RqPA6RAHFpmCnzjkUjhbBjA8NCD6VVAfquJezQG+Pt6CYMeWW607pLanw/k1yiJeuOhFV7C9gp2OG0a9xmGwbJNqph/jtGXreiEprfdlTIyk+If18E2nHjFkZoJndWsx0/+AmYFyhwCd7ImfOGvU0aUIZJej1WhPDB7GM3wsZd2Pyit5MmjCdMmsAMMitRcYj1Z0wTy0UIwzl2XFppUb7AqKKh4raA5/Nhd/aQ+uaf+/rXv/rVn/v5X/z9f/h73/ruD0/PntL28QV4kHknSne16kIurTkWwuKDa+DK490kPhSzc//opS/92l/5zV/5i7/2K7/+8/Su+cTU/fuHNFSg57FW5eRQRrPyRBkVeuFo1UqYuN47Zm7iNLs+p7fMq1R8d9AGj5W3dNTZItxp2F2mgvJQgSjlQCh9+9+ES7hxjiB9VokNbYcTbCOSJ/chfgGxwymsxrwB1zIS0KES4JTM5ipqMSxgi4sYMHkC2bNa1NECa/o1eI1REJlwFHT9TFjA9flFmi7GdUPdQmsSA1swgh2ZgaSoc/nFFmIYdBgvcKsU7PNpbBPWzNfcG+06Qap1JInGgOoVPE/xidsV3dERw+j3cMUFTzRJ11XmvdspdZ4tRq3whNizYXV7jAPcLKfuzz047C9HC77C4Iw/j4je6aowmsiu5toaw5EWNiXW5AOxBEmrRzPAtqhMRt7OLxduaLr3a7/x53/5V37pd/+D3/27/9/fPr26ffO9j8547kIVfmnE1maBTW4u1aIud8Z3d/Zff+1Lv/jnf+tv/I2/9dfvPTxkUnFBPu8ePHh44NZR+LsaRQm/fpMdCmPk6JRQLKVBOiPG71E7axNZ93dzsbo5W07P52ypxmyqQ1l+l9QNnRgUzSu8PLUiJgZRx+JW5wADayGTu+BG6Fp60j6Vvm5FmzN8BmdLYCONu9iHJCMtNdfKd2n4gp6W6habTp9GBJ/OA3XjhtrlRKQ1okw21m23pUay3LSE4H9KvoQexTWJDcnwGr9qk4jlYRAENyfQLGxTOQpUbXlg1ARv32uEQuvOlauO+Gcgbcnq6F64lp51boKsHDu85DaYHPaHh8xSLHmrsLMn/JiCnt3gh73dPT48xNtMdFZpqNgGasyWS6vFPjt4Luiu4r2DPbJg4xDWqloFrZ8QApAOLlmv95mok+Lj6DShM1A0Dn/WDD4OgeMzAX7L96GWfBt0NP6rf+0vf/3rX/ved988+t1/9KN3Pzg+PaNJpBnKdmrSpHB4c5Fhod37979w5+4rB/df+Uv/9H/2q7/wjS9+7YvuLXp9y4YYL792eP8++7XhfdpLHRFqJSDEDYagzq3+KUWiJhgVUxya4ixxYSnfipeVps8dqVpcDfu3fF3Cr9DwURjXId0ZjA6Z/qExXvezrKGN0QvVIcASajB3hYZXSS+eVVe0grY8aD0UC6zOBjvfMLgREFqRMjBcadA2Mq6xDfB2bPgBqAhn8GjjPRv05kQqlDb81ahu6Nb5lkFwWpoGDyEsPrPHzIa+KeKltCuAnPgFTxEyK1Hh6alFCz/nDpLEFHDoG8Y2+zXIQPEPqDhsqIhvIgl3MrqUyCp266Ak2I8dHEY84B0Ndl8a8PrpzSEv0OIU9tyhpqqyXdhVtnFhe9H7fpuBz4y5qm3Alp5ML+AkJyz6pIH08+ss9rY3hk0wq5kjoCA7fbGNjz52JF0DZkm5O7AdyJXfEAsBC6p97V5n9iQZS8p5VYmXDx+99uivPXr4lTde/8533zo+vWIrmg8ef/jg3iMGbPi6xMnpye7+/en04ud/8Tdf++ovffOXfuX589Ov/dI39166M+ON5FnvcO+A1+wf3OWVSGYQrDs+palt6kE6s2UVVKtH4lhQm6muuG79wZ9dZnYov+VR8JRN3WZXp6zbhi37YvnpDr/+udsfH/pj+sctG6W1aipAi+Sw5nbhzTUWM7oOrNPAtqZvEhKCjVkwJ8FcJ3eYDb5ms0ZbB9AtSB3ppygUIHWXrDWM5n4NsH6CoqGBTc4s1w2lCBuksmqxxTzrtaMEP+/4rL1CykniYramC+TTjD4dL80KKhuoKaXIadGN6soS6DUUa2xlNh4mdpEEu4iUARRGx1ZRrF9mceNhb/LSYvJ0ML7XH97rDe/0h3y0hG3waaPUiNcDrnjYGa1Gezzt8EQ3G/Qu2RmRj9X3hgeD8UPfxuhNVxPWzLhDPvN6FlrJ5JJHQeJWQx6YrP3cgXE/99TlrUY6hiDROaQZ9Y/mDM/wFWPO9e6i7NjgCcd8+fWX/8q9BzfzwbPHz54en945uHPDh4RvZqfnt/df/dLp6cVXfu4b47v3+XzvS6/c556woAHc23nwhbsvvXrvzsEY/myGZsWIsZWHUumAozPKe/9APIoCTxZitXZHAcXbDFOot1e9m7PZ1fHVOeOiDIr6KUVGRFkgStPHWm3Gunh9iXcJcWi5xgOVUCW5dbZoIqMVWqKfArSkIlatLr2rL2G8YdBCINpJ2D6qqqpGd6hbeUbMgo5dSq4UgqCiayZpfEPZSdaOoewYdFe4kLRODNOcUiEshkbWnDDIG6xN6GclCG/SO5S6JiFJGyYVQqKVsR1bGUaXBt8kd5A1KYGOeou048ZV9l20Aur3ArxsB5rWtf/GhPIdOnGD6/uD8WuDyassv+KFAJzQ19/GTEQP2TfskhVlDLbzctOYWXxerb1aLJnsfti7OWZbsdXuq6zz5CV8b48Mu7i6zR4nFVzfo6VTDTyQsRKrOs0g85O0gS43QY00juYNMP7JjAfDm/aH+X6Ds0+WKFmjFRr29w/GqLD7xVdffvUlOrtUM+Y5VoPJnNmR0WvcOOZ9dtpdjSbuDXzn4dG91x8c3WP9tI6kbA2Xfy2uc7BW1mtJAFYFgaa5bVhJ/OFl7tnIVAnrXPHAxfQp0xI3N77TiGszLqzL0bPA9yZsdM/nhFmNVAtlEKiwlF26B5ZBFElZrQutAir3eUfgWKVL9wpbrdwdXVLFg/CpJHMpDUSqEJRUkUr4FIOki9fJSLp3FY+klj5haiblWUieAYMKoKMPnScrRSV1oFXNE8qi0E1O4sZSHe5nriWysDmvxa15hJfg/K/T1xQJJLVjVTgvYjas5IiUTtdmiI3YLssxBoiVI3mV6SCorGGeNAhUT79BfX+589Jywu8L8/EnfPOBlwlZLsOaRxZF8ubt9Ko3OLER2T9ajvcQwiAmi5Y/YF/g3uhl28/bfaqgH1Gi9trrZJKaZmCUd50qr7wvpkJ+6Qgv7fGBPvJBWTiRHeegSaIu06LSHE2sIfxBs+B9BFTN5yGoADyVMSc4mbE9G+1aFrzh2cO5z2ROt+yyCRRud/hg997hwdE+n72x4+SbyvLz3lD2k3nssw7ESjgwltEBaXmrHniBBy04X6Nndv58Nn0yv3i6uL7iaRNEh2L8OCJ3AfZK3mecmWaQj+3Qc1BAMYko95zTGNSrXBMuBE1j9POOwl1TWKmJYBsng/7xRxG9yLmEk5J7j+rFTdb8w7xrQzTC1oE85ZYCBYe8MSqVgIYGSuQafJEFUEo6TCqt9GH+ufh95ty4fAb+AmAriwaL5xZGS0dGdN9Cb0g1oBFaTmQxY3+f1r3jmMwF+WdgJN9UjjViM4P1Wrs0GfLDI/jc+n5/79F4drK8/mRx82S582Bw+3SwmNq20SA4qc2y0P71mbWTkVKqFsz7vavF4l2GPIZ7hwve+gUwusP4KmOaNE10KqmcgFJN4IJj0RIimY4eRUCbadeUiQ06oCkQSjdrvu3Ngjv2Q4eoyx5vKwYe8W5GX2k5HYNlnQBzcPRfWimDzC5OfuieTuDuYH8XP2TvflzXLPpSCFN6VVs1jbnmKgCU3GcD5pTbM33jHt8+pOUOvWa00vD2yJRxquvF9OT24sn1+dMls/NLVvnxXRr2NN1DB77HzauD3NQYm+E+4bioLV8Ym/fuAIAFhW8BK3FdcbuoVxDbUSHskeyYjc3BTSP1q4Ngx/B/QUZIJOv+O+xPXdP6hX0pmmTiicmZMCcxrAucCxgSkU1HvjWlg1Weve9GpTApDqHdjI5K30QFs4UL/MIZ1g2jhMQA4SlawTrxQhqywc8cwdNk9RfzbfAJgZAsGEyUM1d+HNtSNqw/UwKiNVRtUyaUD+8Wro4Ge6+ND54tp09WNy+tJh/xVEavk6c85taYLcAVeEyj42mTOHJNKT416F/1B2f9xdPeDU93l/2dR/Rus3oJM7BghEaLgmAwhxLNhmuA8DG90Z5dfr7azoOnYzOk4pl5irJD6lck6Gresmc2fkvz6n6gvv4PIhObh4Od+7wo5ELvHT/JS9/WexnZYcbS7PHVM/7wNUd+aJARob/HkCDExbFIM2HGWBk21bog8TBqe+z9QYtSIP35okfPkx2cfKFkynYEs1v6vXO+nOMEqX3RCR8LZm6QFUhZtO2m5nYeIjNcDLXDUInuLl3KP+m64VGYFYdXab5F3viv0XLv2cTM1pZGG8IXJMAkNml0kGA5bgEgtRtMsxEIdlRjsMahKRXVGqh6oRo0HMJVEqJbTihyGIn0mXyVKt25YXRRKbcFbsEriWRd6YVDeZsjqgWng2/whXT/FWyZbDnfcNkKVVbQClyIwzq3zKpudLqobHQeJ8MJX2h6ZbX32mL68mJ8d7C4GI6no93eGPeT0J4XD0SXZw6i7O73JvvsPkgCIzTvr9gZke9YMCwxP9B6tJWM4XC/psTsx9HhdMf6PATijThAhNrw0GtlAZmz/GbNR0afGr3yT2NEidtm+h0Luwe4IgeVfrI35KMTewf0BWkUWa+Khi4mtdXLHIfZTZ556oTOYXPWuCTXgoVRE6xHouWu5D0jIbi4RkfbuCxvzgqhxYDPPLGn/eyMtdrsXoFK0NN5phvKam1uS2afseLx/tAdZfZorjtm7mCQoxVCclqQdlaJFP0L0M9Ekp+cKmkrGABxDou8QkQSKIHAtySvMUITooRiA433Ih8heo7rLLYp0szlFObN5l1xBjO21ZGpaJ6D2HExomL82qtMlZKCAroWtybYBCD9dHIH4vof59hm01iYWZXdiLAWCdpWqghLWst0RT4rXdQ1rTyaUC88biGKdoKO3OTecPxwNHl1Pn64nD8fTm5GzOTP6RLa/WEBDcMl1wwEupyNto3+F240Xc2fsiuUH6PNriqDnUf98V3nFpjiYH9RO5B2HWkT8B7WNicPVGCciuiu7ZtjOaVfPNACQ2Gk3rKRoOt3WLZNz1gK/NkhI8eTFO8KTas43B3SwU1xK7MEvUaBK9wUpgFiUnXn4AIhELqaZQ+rSCyIeJGZyvCewOuUrE64Wcwu+2xJPj3hpUGn5tl2EVXQg4+3TfbtP/MePYsW+OIq84R+UJV6FIaRigx4dgfB5DFaEUrERAJbWB361lWENfYGXrzJWVhtWICRSODbCmxIW0jGom406MyxRk1NKX4xa0soaeQoBg6LZK8Ecy7hLZeFvZHSUf+sFTMlRN6fyfZnAMEN5ucnNYVTumsn6CzekXRX1I5BtGmUrOxhlVAUmhhrgo79518bJvUzFbCQ1ELu6Xexe5rVenc4OhwPHyzGj1aLT/i05Xhyu2CHlOmKJWt27XhrcO6TFo94tnYTnu1oYtgP47K3eII6uByeRgexP+BDTrQM7AhFmzC0PdQJISVrMrAXSjPIs1yfQVgzSfNnx7V0BWfFhhHOjbsJgBsFW6dpzaz4sIWQzqJzG7an3qBJLzbFQylkDj6xmXWk2U9Y+pk+WJW46KRBsHsg9Idh7WoBvta4nPmx6+lznHB+dTK75usAUx8RyQN3BbYk4KVEn0tZp3aUaXruLNxfUMdMRw1PdaijgnIkUiokWDW8JdZFncJpfdmQb2HAEs2T2yaTxBgkSMXd7EUvYWGqlVqaGRec3xa84kmL3pWtkCcbOXVMCi3nDeeqzDI2Jxo9VsY8XusPYHZba1Towk/WiuMfOzbAlojPDVatltSjMlXhT527pFxl3tEUmQLVJgpIanJ0Tj0WUof0SVvTR4WOfYfGVSHhmkBLgKrwMYydPWqUL8XvjcYvrRZ0SqltZ2OWxzAQSU/zlnk9lm3zgenelPrHslMfF/O4RuPUn/dn58vBs96A9xX2cAyGRnnjyZlt+q/Kp8VLPafFMmBXjjcM7LbZ5wNDohjbjix9Vmr2gKfP5aRczXWlq7mz4T4cJguWYuqejyl2EHFA20Mzaf+TK5wZZZFxq2XpmMa+1geO6guoQEEdQc2wEBuxzlyyML9aTo8XN8e9vDu/YDzG7jEZx/sYfaGdRyZOuDsaHY74/LiZkodKcLFCm/9OZxO2j3XZbYAhrmhpDQ4WqdsLQdKFgFHEseuGfBOqegSmjX7wN6RihW8YmuiR9OCuHREQ4bpjFZpnq+LapAVu2silAeQYuS1QasAeBFD8JSNC6CN56AuV1Lyi3KB5xEYEqKBV/iWTtgHWQGGfOdb8SREz8QIaDZdcc0pCVQziQdiIJZSslv3Jit0qDxOKWUXW4YIn8yAUu9R5M8vXvHpjbvoMkeBQLiXFCY8Xw+lyPHXMb9Gb0BGlU2rvtM8y6dvxajzpLXd6nJ2XwITLaX92vBgc0QbSj2M33t6Kj58whjPv8w0GBnlwLNTDVWx48T3msmkx8A+KU7emWuMztkL4DWtQeMNjtcd7687lDVg7w3uOtMFkT43JQnzBJ0ddEb+AmIRUUbJZfqcvCgwYWs2jy9KR5bbD/QGByodWePDwf7755v6FfLBxmg9LnC6vT5hB5S0OPsoBnR7IpqLeR7gjkEdadRaLsrUhbslkDE1t5xmbuqro0mWriNTIW31TzcsmLAWHgLBrGTD/onHhmiw1LLHbUWC5a3LQxA5JIQhuwopf49M5UAmVPzkISaQ2JiW2IKZGToyccOF3rIzVjaDT1xJriAaJOH4mBJaq41E8vbQjCr+QhYbSWHWEHX5ZbhNLqEQUsJF3hgC4ZthRgf7pEkzeSxZG3Ag1i90Rm1U+Uk6ViQC2uqOFbRrVmM8bzRfD6+ny7JxvVbtJ5vBoPr4/3znlZffRLvPRVnE28mO9jCTu+M7k4YrX+Cgj3ao/Z0lYn32Ep8ez5WREX7TP2AlTEdgXJ/ahjfbCJzpbPA1NN9UM4Hg4oI0JtZlWyXxUcSASSasFi1YZpWGjeXrDDBMxcinTnG2RYiWHO/Vx+5mxEWup7fG6jZwskVgtY6qiKwB8doQaOB6pmW2w+eFkruOhDWRDezril/35JXvaz1kpypDMgpWuPsHyoMsiNV7eZSdiX0axJWRUholKhqDC1bPlg7nqLNQ07beGVKBw1kAxtg7gHuTLI1gdn4oH/jmnEGzXjDVOY9nia8Hr9ARKKyRi0BBs66VfrzsXZirlAZ1qit1RN54pFAVV8ZBaPy6WOJeMjlqpG0Uu8Rgw1yZ7MdX6ndQ1QnEV6wVMOdRR4CLaQiGTUiC9ZaBjC1WUyLUjA5OMrP0vmQNBfo1nh7khM6ErNxuGJhFEfiSOF8udi6vVx4+X7/94NH4+uM9UPd/TezCcnwx3rvi8CarxEDS6pnXyS+yQUbPZ38iGcdZb7tNw0ZnFSW6Ww/PV8N6SFdOrZ7ybyPvuqwFDppketNGzJwm1Uw48K+I4Zga4Mx6xgZtWo+4cN7BcF8yAMxRkvadnOyPI6s1ZnxmUvAWhC+X1K6wn49hQRo6RWrqWua8WC2tDMTxi8g1G0uKFZEQ5zEDorrwsz7PodMkuHosr/XDGJld8WILdyvluBzs+0gZaUWwI8yFxWsJVz+djZufzCi91CeeHex0VKjOvIZbz+lhjbJLXaQ1Pg3dHhdfxdaBLtzw5UnHSvLdoLknoTpaD0M3JWFXDxraS7GakseCOBXesFSrOCRhP7TJNFgI0eB0BmcKfP72OU6NtJKDqhCJEhSKNMDMkPvCCvhDyXrANrkiTmQinFu3w1gTKCrBBSjqQBDr0tUU7QMddTwqyHhUWzbEqC6DBJzibVAwjkmSm6I0yofO0O73tf/z49q0fLJ/+aHR/NDi4v9o7WIwezCeXS4Y9V75GyJ59zkL7zgPVFffhAY2VpVZx+ojsaYEH+hI5s9Yg8F0UHBSHomPGjjO9HdqK5SIT7tR9t1dSQZ8Y1YVOqRqn4FSN50UOJ+poZ1h6QvvJArchHk+PFE/hOZNV3bdg9p2H1ClwallS7dIw2xd1XkOICjOSBEgCkPRcB10YFVILNeCdYd/QdRcPNtjh7sJCoSuW77FKm2ZQt5xNGZiCAOejL40H8vTsaK296BEvoDAtMWJwi9FapWra2Br+MXMZPtb/9MkkdVCzHBB4dFHDqiiAJPnVoZiKhEUH/sx1mxEEqdHCZJa0bYTtcBNTmuWsOEwLmVGvHjaKUddoUALm1HFriETXkEIxIWxemCe0YDrEjhXXZLbybGrMvJUc5g0n4FIqkEKrkgmfjn2ZE5wO0DAbAy5ossWigwvqioT07nk95RTti6YZRir5BJgz4hLhSoMwmS2Hz05nb71585M/m0xOh0d3V7eLFR8B7N+djG7mkyu2AF6MT3nPacxQBU9JOIGtFHWZGs6reD3eeGLYdLLHG8ATxkt5OCQ+4BsyODCbYgxYwMUW8Vdaz04pHjVcMvdgC8XDI897TuInRzwN2tAEUc9inRcrMNWdz02P93yxmAawz8w5UyPnoz6DQHgUk3isDaX9RKn0EViIQ+axB5uZ+tTnl938sC8h/A/uSMX9cb/YwedMGkTyxifN8HOfA5mU511Bvqx0tsAJF3xt16/QoB6vVo3ctwJXZGkonU+0ZB6GJTqZNUGhYprCUgdL1xxpMY/uajAAT+32KGADXKe2QCtWUUCq2KcIAFqrXuRTmFt8E+w02oI34YFwUuuwkoHG41K8GmL0ElG8Vt0KDVgAYSUZ4K4B1ADWa2GVnpAt4UZexybXCBXXABBoA4r87ROMOg0b66SC/AI+aeS+SzKtIoFs4I1zh/oiHmhUiHaUJ3fkJBXLxnh9Kfy1LCugVmBx5875NR+wvfzhP+w/f2fy6IAtZpY38z7fL+FdvuHBaEh7eMxnx1hUTf3PRmwWH9Xa3p43RTZ911QOZOql173VJz3eZx1NhsuDHtWXYZwBs4hknPcRqbjM7NNc3NALplJjTiu36qVgoicnnJHW1oMnrrK/IthWlxeNGc1G0o3bAqCBswk8yzlZgUIcPhjC0Td3bWuB0lFO5UwRKghDcN9AAnppCnyZ1h7H5sGXltAxmOnp0s/N82N52oKnVW8VtHQDJugdWOKqU7I8z9lPJmdo9Ufuu9oVASJi78pbbJVcJl1N6iBZbYxopBwF68Imwyr3l+B0dU1sUiQLIdYQUpGi9lxMo4wncaTLuSLhH7AhLQaCtuFqz96ocnK0K6mg1FiWmNEZlHhkhAKD1Vodw41luDV2IgjvRkeV1QQlsPGBEmwmm9aFtnVWfBFveWkALdPgGpJnu+8RC4J+lEwUOzlVKIEuEyIJ13uoQVqgtFkjd0S5rjWFnrCCShttGz5K2Z3OR0+eTt/60/kH3znaXbp05Xa24u113u9lqG/IyuS7o97LqyVLtJ5jLEZGXNRJleRTTtRBWDbD2sdj8+ll79mAr8pP7q4YLRwfrmiy+tP+MK/DU8sHfATK0UxHZZYOmeqHeohDLM7f+fwH1zimy8Y0F+OQlidojP4PGRfxzXjrCtq41lRKXcqc+aSGa9BE+0hoGvg4qs+09lGBAcVxrWo0ojTILo9hknJIMzi/xfF4FPQhkFVp8ytnSN3zw1GczFA6HEokY7mG6S7v3vBQPR/sLXi7iy4u/JOpZEiBHOSiAgkHtD4lYZ26BncBCU1NqW+htWybxyZEioYc4lSpjgJ0ijunQguKpw6jArL11RYT6kFnW4GG/ykSoFUHc65Eg8Wl0uBCNK9Ta6Eky7mhAcv2RBtiyY01JMOtGlt+JVEu/Htp0jR0ErGK+AVOIEhrPBFDl1MEVdqGGRWEQ229sRReE2yCNOXIJHWGDTil3SgkCwcuG+LQptxWjMeMz6+W7/7k+ke/P755vnPnPivTltfXq8mKH29F8KL6eHy05NNovVsav9XyfMKnWWxwnLRDBYYqNSgQ3YIpRD9V6wz8gB4dbWCm7PtslOgkPoP49E7ty47mLHvm47vq5XAIXPRHn98qu6lWPs/BVBgNJm5FhngMY7CUvNOPdRSFrILEN2oI4IfO2unRTIfwFIh68VXQzbV+Jg9YwpaJENes+h1w4rSmfVtCZgX5Wvgps/N8sornQ/rB+vyArURdJMBT35Btg5mH4EWv4cQFo4vR+PSKuZnh8N5g7z5v18NO9bW+t5MayrAYUy9UpA4Ruvt2Qbu0XKNo2CS6LkFjyY61Dab8Cal6rRSTG9SUzRFEot31xSCxaBRexU0E2ZlgjU0YoPCKhFUJLXixSPUuFy5wsNGNcoTEfxg0JbsrHZ4gh0ULAyi5pqRSRAtRmkKtrpuukHYY8h4SRTrg9nXNH8zSOecKFiLhsEmLIMjYNkKhmaAScrQSguGlUMliKMyq5EmTkxEoqPO709ng4w8uf/T784+/e/eA71HQDHLjv11NZj0Wuuw708Ce22k16P7hSzO+TW0R1wOdT2ouo6HFsj1jXZuND/WN1/IZtrllO0DroI/cPNdNl/2LpdNo+3RHF/2rlZOHrLHZZ5JSjVA9rwXrJ3JsqlYgatv+6MzsREE2dUK8yEA8mJ143SHH3ik3CLSkH0vrjMPZRQUNj8M44jPT4LjNwIXjNO6sEZcZX6fyUZN903gZnzaQheNzh2FYdMDIDh44Zq+NrJgbMB3DGAwL30f43Pjs5urjJ30W/uGEbOA/dlFD3UaxBbnR9VMq8RvDKRl1rGIJoApJgCQStCOVL7AGJDHsyyhiWdQy4eYizoaWcIQUZBsu1VpKjA3T0FqltthQlVtuvCOa2NQuTWXTMqsmOaSXV64JlUplB8JryWqnUI6Mm1s+a7rwIiqHxqpdCiZiww5WB/XKIefygYp//rkot/gSXIuTZCtpm0PpZWLjYLEZjvlkEi6VWEwApiw3evNNifHz08Xb37/94e/vLI7Hey/RErGlDE97K/Yput4ZHCx6e4xRTniTltv8Yuc1Ol2r1fFyNeVtJeo0D0E+QGXze6RY4aMlPUbaFzYCXK6eMTbikpfVngOkYDDUSXNEU+kCN54KjwYr5hKZbrAdxFfXhWBWkKITm7nYgibXIUmHZX3YIJ/2Mzl44Qn/ZwYDNJ/a3LbNfShSCN7JFAEJ3gBI52W+E091f8UhLk02oKVzO2P3XgadwOQuEhHIZMyFZtoVPsxDsHsFC2LwQEZm8MDhzvR292Zx/eTZ8+ntKwePdncO5veO+G79diFYGyyVZGVTNiojXpmtlZYQDtE5C2wJXXpXrbr4Fjqs1+YKuKNNJARFVRaNlAjQhsnvmmnJT7QFDUvX6U/clKZ9JGydAl5zMyDEO61norICiElQGiDD3MWr+BfzDi24RfBZkKw3R2m7iZNWojagzwlZLTrEzsBFt0XcsY64bZnmqTW63masOEVWPCuTGzWqRtP/7q32Li8HH75/9cM/Xj79/u4DVmNPtA7DHrMp21vwlYb+3q7fZBnt8nXCwYB3BV8d7HG74oZ1POid86kI2i3GE5nB1yV5QIuSWpIJdle3PGeHwUyz81XDPb5lbQtFW8SqT2fSnK5wbo1nTEdIGNGgJDAGT4lVODXMgnPijDZtNirMbSBVd8cbbRXdjnTVm/FZCZ7Ikntn8Gj0rI9pgfAiPNCC1lDUNpxbS4GM49ppdfKCiUKccMnqHF4RxEstE0zpfIMtu73QES/vMhjDjlc5087y6mRvdLsYLuf7i9H7f/aj3YNXXjq4u7O7e7vvWrwUiIxSIjltFZ06kFhyWgkF54U6AkJjUGlEdBiNZdkWbsNQEBBT26VdhQArwkYTSOBWuvwnVgq1c6WHvDgmWHzUIOla1aAYdQEcTYqmaUMkFglVsl5ocAsl76I1fuHqSZI6wslk+K5lEN3YoCF6CfJWvEzSAYppifoUcgOu0yKtxCFXwhTaNj9Ti6PQjrJoOolbV1VL8fkEtzNb7ByfLN750fQnfzLpP5/svsTL7K4Om1/35xf9/tWA6Yi98XBvj73WRvyc+eblwv5qF6/w3aLV6oJXJBjt0IjskEanDyWqfcJt5vNV/9qBTdfB8H7+mSOLeILVWlbsj8sSE4ZtlkP3sHCgnyYu295ZJi6AsWvlEIvPdfRPcSSNgkP4PKoz8MIeFYDJQ7yS3Z5s2fBn3Ym1ciTxiOjkoYTNnJa9RtA97DozT0FXmKe+YPiUaABlxHBVnXLRHAiPxzSD6I8TGiNv7PVxOxtN+Tzo7d7B7uT52Xt//z88evm1yZ3DyfjhNXeeHFw0+3bdIbouMDvKqgckpyIqlatQi09ovBOFGxRb2NFfDp851mJI0a6fORqNZfeZtACUaxF3qS8gljJNE3PJUQiBlT2tGIF73hyNqvJOhH5VSJMZQpYSYI7iS2EXcbtUhLQX4gV98fwiTnFbY7yYuAYbCCZZ3BaRAjBtW6qIG6ykhDb1rJAFNjRC3OPp/+1csj7mw/Pv/9ns8Q+OHi35YCjPYc7v8cEJ3qlnloEnr717vYN573A5Wvp9bKbwGACxpeKVXNo+WoseY/doiAsY46cj5LCms4sTYxu+MchmGTwc7rIVy4oRmh5ru5gZp+Wh+rI25WzpRCKjsYxz4gB5NMAJ5Ywz4LTkxWc9HxlxRQuHbzfxbMmUAXmbp/QYUWLXbbqX5IImECyHXPQfP+GJ6XAkr5ovlYJiTgPr0y33DO8itp2ooJ/T02S4h9sGPofH0fTxOary/DwNuvJ1Qn9+eju4vHKh387o9UcPfv8P/uTjL379C3cf7h7szkd3XIdrvzkFklMzTgYoAk6BtZOXFLkpyUGVm9DiITk5kxX/Ha3YBTOhsCuUaE6fjhdQaJGG1wtyNrTeJkKfU2EarFDD24DjPjIOCPYhbhy8n0Z54R1mITty4A2zXPbzHReMzx5wVFS0+2xqQUp8c+ptpEa8DVqHP48rsAhK5jrexbcpoKjuhtHCQU7lk4IKgUkns8X42fHtT35w8d1/sLd8Otk5ZBNu2ze8gkXYvBzIx20Z2Ns/612f9695T3xnNBlTTxmj8dmcijlejnYZmTzu9aZsgsigja/1lnVBqAC4TH8vrkfLWQZeqOs4K91cemo8pTmluHQQlSaR2sracZ67cIWZi8DwJ52NH57W/IYo2stbZ3SdCo0U9VlxOIzPcjcMn9gaMmDjXKKvVdgblRc9Y0d7YiGNgKupqbeTvKzEQCsq4aj8UIcXrIZO/+uBuKRDsjS8zEn4AiSKjtnzeHYzvJz2r1jM7tqde6+98mg0ees/+O2HX/3a7sOjnb3dBe/7r02v3h6o3BVR4g24hlXjEmgREKwqVOgCk4l1VCOQkTU2CbGSCOvAdmpRtsRgNOLPQ3pRekjFbozX/F8QBvTFA4Lc/6AjjdureZC2sm0yK7cAkGwpG+X3GT5F2Hi31Jg4RFW4IZS3gc1hpDXoISwvN53sqNXPPLbSwnEd3/CvuhoOde8xSPKab0cDLB64bga/88e9D75/8DqNEHvU8roD/TqeiFj8cjucOcKxuLyzurzoXzDLx4a5K3a2pVLjEyykHo4e6q42GyfalBscbSIjNDU8gyjdys6kB3dS/IJVYA5uMBZK23NrRV/yFgYzhzgPD2P2cvEINpPhPX/aQ3OYYdcqGHU3U2U7PMs2TxeNs/Lth+F8F3WQxqALsw4I7TIOngah3SNZbrKWAaOyzh7KmQz4viTNZZpfWmNzys3CD2z4ASfuFLilL0Ay8IMhdubL8dVieHXbu+HlLpcV9O7sf/NrX/47f+/v0yn9+hdendy9Ox/dvck9RAW0jf9WsRePxKkynR+KAyI5LfzkfLtKRf8Nj+ZBG8A6ZKFwfApfeYA2aig4nYdIbtTNTKVLWDQ2dVnzLE7JHpRRGhqgqRJNttH8mjZEKppmkl4rknluyUFE1ZqTdHaBwvaYFBO3jyCa3DKVIAiFF0kNHUiiKYFK6Jh1RNuMDa+xCHzKzlF6TSdiIYeFwaqBqW+SdqLM3Zi3Io6fz9/+0dl3/+Du8nRnj62ZDumQl/ulb0atmrGqbHlxsTo/Xe3yTWnahyM2G8zAIx2z3dXw7goPcgHJaLh8ylQAm8DTsfNhDRlUX9sqZtF2h8OjFV9EYWQFr7FZA+eaRsghTCcNeLakLWL/CDzYYVYn1HVWTYvNdSqiKdxMGgIVhQQ9KllFHmij8T6ejAD9nbeN9FGa7CzsRC3MQIE7pAt57OSDne8kM7wbPvECvRu2OqH3ER78hjv4oQNCBHhxCQgrBhiPuZkPr6Z9PsntDH1qx3hw543Xvnzn3pu//duv/eIv33nltd2dvcXeDlOiSsfF+ffKuR6TSg9hhswxJmm9V2FNz0Zk3URQOWbYxCqFVHwL6rlqSINuUELcsDRIgiSvyQ0XesOSVZPpHZBjHSepSmmNSiCpa3YBSFTWl7MqkB6I3QRg3OHNdVpCeZmcawXMc2q0NaOSc/3UaU3yKXiLFm1Fgqrk4tzEBfAp4obzKWiiUctQ49yVVxKNpEDljeHW0qFi7mBCD+rjj8+/+53VBz8+uMvgyF6P75ZAwCrtFR/w44z/Lfp8cvribHnynG25acdWrJPkJUA2tqVK9Sd0TB1hHfPAxv+k1/uEzWZ6rEdj0SlCqa18DsWe2+6qt7t0RsKXX2lW4qN0/Bjh4QGMERVm/JjQrxY2t0KQbQF9sbBsVTnGdXAlfNYMJVO2vjpwPJY2EZ+vW4DrVHmDSR0R6tOjozWNn+bgp2XsI/NmYx5xcbg0kjiow5qqibvSD2VwWL9zkRrPhO6xwXuVY0ZEr2b9y+te9UXxL3we7713+I1vfu0Hv/133/ud3/n5b3yTbw5PJiMHkZFqr8Gqx2ldbsna9skk8h0FzX3+PalqgVN9A6lToYWiMraVVtAXAcQk+fwkZHh/AKF+IlufQpJz64qgpnVsjRWuTUHC7ZAw3kbcxLABsEkuoBBKwohHTBTeMohJwocyMyZap0bwP3sS7XOhnwVqzzo+h+JT6B3mC+COPmbbMpZIiZqHUggb4DF7t7PJybPZez8+++6f7NycsK1Mb7Lnroe2RYzK8IDHZD2zcAxSrAZXl6OnnzB04uMbu83gAfvj/h77i45HvMbqVp8wZ4zfUfv+8HgwOoUkA52MKzoAw2fnGd7xOYo3oKjK2s++B2Md3PzyOMa6M+CsvsQbeY+RUU3Np3+ZOWcaiDg3bw22QrTskK+shjGdZ0PHXn07kQDvE2UlAndXe4m+saHYGjKSGtKq0DaL+rtOkgdB7hH6CgOytPA2tUOeC8kLdxBeiYTPcLCgKzEbX90Ozy775+e9G+ZJWWcHV2Sx+mFy+OXXv/rgwVv/6Pfe+LW/ePiF18Z3DmbDYXsJUw/sjmjRRcxldxjcilpHZO8pZRm82KFFY6iOutCIxeE7qPw0Qsd5rcaG6UYkBojxY/Oo0qVFEyIK3spLkjmtfacTG7RITia62gqD6Cxdbo5e+LXuqPy7o8ubpS68UmKMxDu8n31NDlvyOtufh06mAW8yvcHBcpvcFo9CbcYOKKdS0EqYLBVup7RZ5JFruZrQg/r4w4vv/enNOz+5xz2adx5wQqgYMnGBCI9nNINlBpqo2+H1yYpP5VI3aTD9Uu99t/RzeozuZo1s+CzHMpHegP3zj/ujE1d249E0UTzmuTaNIQ1c1MF9hx/xluyMljKjE6Le6YM5Mml2/YdHXoPCAdAFSFq98j8fJoFqM8dUwgcUGiLaLpRhkbdTCL7txC41rXvpXk3mytejYAeRUihMcs49weFQ/I0gCsLLBi/vc/Ac6NtZTs37kjA3DD7sdL0cnV0Mzk57jotyz4KzxaQfQvjSQ77p9tPf+4cf/d7fv/OrPz96eD9jWt4mRCvTciFsbOuAQw7ArbZ76aCbgEiBBrFoPG+iVl1+G/4mAYpISKVW6zp55X/rqGgRvZhISnEBOxxeJN2o0GkTJlvsVSOUdW7cZevNuA5inewOglAFx2oqIJfcVrZYN9wmuZCbekZ+xvEpBtXC5h4tQVI3llzzKQUquYCxhgYmajgBEIq/KgOleu7ObofHz2fvvHX67W+Nzp/tPVz1d3eWbF0ISxZJLvOKLqjQ8cMVeUabzocXl/3haX/0pH/F+kxeq7jLCRlsK8Mr8z45OamwwwsNg8n+eHA3i56vFze3ma7gDX2eyhho4f3drg2ENctZfC603XEhSxRvVc5nNGYd+OSo4yFqw2SFmXZJgK1dPM6HUH9xIcsH6piAPiFrqi0hG3D6vAS5Xywch7HVs9NYPE3DN3A2BjtZqOAbUh56JoOrpNHCZ0s5M8gjLKtC56x3n1zcDM8uelc0g1isGliIMHCK42D3/lff+OL3vvvut/7kjT/9k703vjC+c+cWO6uSdu0OgsHfjpsskP8WDEGHl4j4Sdc0/nUHoULsQN1VAilCRSQBIxuELpRruHTIHXevqUqq1qEVWReTdbDEjTLhVLESVgRFb60UIbkwGCck1skvwvVZ6XAVIRqIlnytMdaBJrdJIyaBuKXhGo9AuGwDEo6kgjZen0XpeJmzDqlxsxatJVaiGeSjEsvJ1fXg8eOLH37/6u0fP1hOfdV2l0VkPNFBwctIrHxh9afPXZYPrPnRubyZ7zx+3r9ezl89nO3fWRzdpx842MHrfDrkjd28kTTxs2q93dXgTm940Vtc9AeXAz4exv5QzNSXBzprb0tFVXRwxKGbDHgw3eEGMr7qTpKug73YXs2BG+ca0QNs5uId9qlCMF96INguUMudHwjdRVXCdYXgcZqHoKKcYJSnr/jHZ+PL8UMUc/E4mwfT//Vh0NeQubewsM5ZENbl4KsD1oPuTfs7F7fDCzyQD8LQe88CN25DyZVGw/iY9dHDb3z5K7/77T9+/od/vP+rvzp69AqLThe2o+pT5ZUyiYUl9ii4KDnIiHkUbqDBvH8VmLQOdU2xRSws6UUsbuErJuZpVB3emrYpEorgonHzi9aLTx6adGXwbw9WMH/qtdY4qWvZxUYSR5g82rkuW68ymaYj5PDa/lFlnaAcY+rXlO7Qk8lGJUy0OnURoXWI+7mHIjvjd0jrjCXQQT9FHjBqyaAdWgs9d6Y3g+Pj+fs/Pf3ut/rPPrnDUhY+7oATUuvaHhVM4tmPtA1c/2CyWo6oeTdnt+Pd+eFZ//bt1cOHq5de6k1Yw0VHnllrOmF7bA6FP69WB25mMWQT+Is+O+Q6Xc12TxMmvRnCYf8VGje6r9RphjN1A0cv91gSbtW3JOlg8uk19LU0mfBDOh5EBgyrDDWI4Rem/pwOtH/rY1xbr2YriUA40WLicr72bk9WbhZVSiuNL8EYCTCYLMWb0GHFFaWibdRgqqgH6qB9lg/tTxeTy9no4qp/zvTpRZ4GMZOKptLVbQvfH/YODx595cv33vzee9/91is/eHP0lS9NDu/MhrsM0aKJ/xDxHwqDFhFR4kkzkqPA1h6Ri44sV+JnzqKVEiaFKHI6oUAUUXQwa6GK51wQkeKmEkiTOARaMnSNsmNW9XvNkQC66oyCGk9ZhZMQA8lFEjtCiqExXpOtdSu5cbpwr1PQuS1RhQmu+5DK0qqhxm4qBKycIrGkN7SG12kR3K1Tx6WByFjotjA2QQWRbwFkMoiIA2AC316hAl30P3rv+kffufjRmzs313s87BxMejuMi1LnWLvMND2z8Yy25zkNE9kChZsNRL9/c9v76Pni/GZxsLv8+utLdmTZ4b32u6wJpdYxuUf70+vvMwrKllFLv8t3h4n41ZBHJmzLawh569UFX7SNYLo8k7Us1PLYi2bVvbZoOu1h6jrZRYYJ9zKnQ6n2PH0MtHj1QB5fdVzrhi2kbsafJ0ZPHCyx/YKEXHSWlLlumtIMBVu6sf7G8V5I6K5mHpSUBEBVVzfguWEPnun49Lx/9rx/wdPgOctV5Yt5Y2w5MqyKIAKT8fCVR1997Qvf+elbZ3/2nYe//kvj114f7ey4N0YwLJJ12cjEG4J/pnfFVyikFc/QcCJ/rTvWQdbXyqUSclALpOXQJpGQWE4mJHGNXWlEUcCztadLRK/1oX7Wq+jVmGylV90rwo4c2tRF2RIuDyzuax2Bds+EitpSt3mP0HbApHysFEQd2YDHpVOlZBsTKmEghkJLIImCCayzKubnH53UqGxE+k8f0aFs8AJf8rZ/Ox+yUvS9t65/+L3FR0/3egsG3nt32csel6AGs+CDaXpnJuwnwIMz1V+1oyx1lxHTp5erDy5m+3yEjIVot/17e4svjdkjm5awt7PX36FvSetBo+cHtGkAF3yCt89WEbCh34hLsO6Z8VJXm1DpG2cXlXF/1RWZS/QGoM2YcL9e8LJCHNIvPXmnSB2Vld6CtrSGOosdGx8akULF8K5IBvBAh23tfuqccG9VCz6wF4lreBKkhzpm9oaZDB9STYIhqUNgO4vV5GY5uZ6Nzs8Hzz/un572ri4yHgNO8ShmuV0AQhGaz8OD19944wfv/PjxD7539533h1/+xvjwkO8EmDntapkXmdKMBxCDmwu14AS3SBEePFATEP0zR3Feg/USj6jUCIkK7ai7q4B1uLFZx+VBREsa9FShgD9XE/HMg9kKO1FLn5Y3OZbFwkXMtIQxUCORy4tH6aQGTTvNCd8isA6oDjEtJ0pLKC5B3dAWMGc5/kc5kp0S3RQocdJSbOWB6LZ93xCR/tFqOb6wGbz96Q+u3v7R6vqSTmj/YLw85JPO3OtpUqbMTzgDYTNjt0P7SBvlStxiOZz1dm6Yxp9Pf/h08NG099UHy+GBLxLcPRzcA5MNH9wJ2METBid1M5zqJq9H2IFk7SXvIrhM1w0WmRXgiSv70cRu7jNo85WNdDEhby3OrpfuPsqjpt9Ao0zTX6Ww6DADYPTzdjCHlTMUOF9Xjg7Aoo0Zyb8Z0VGTDanr5W6dTChXW8uJ+y72adz8aGmN3sCasdDJdLZ/fjWYXg0uaAbPeucnfham+sb0k1W+OGNJdIFpbLc33nv95dfvP3j83k+/9P0393/hF0cvPRwc3rHJLnzVglT5NOXhY0pCLdowLZMSIX5CqU4JNTYp9oZWoJy1lUcxTCDxnIrepI4/yAXEXgmUtEYiLEbDulsJapd6kotIuh95goPh4GIZNYkok9b3eDQUJX+MGVhyhHNRbKlj6GceykGHIg1Wq7aNFaCOyxbjNSyythN+piAS1mUXjltCywCRWIqAqWKhoUFY7fAm7vHxPV1plQAA7N5JREFU4r03b3764/nHnwwX8wmTfIe7vf191PV9P0ZleJ+VZod2wlEZOTQtK0MxCpbdh3A6n713Nh9NZ/THzq6ch/iln2P0hSXg3MtY9cL8Nl9ycohEjjRiVHrHQhmGGdDr0wlJYjaBWY095t6VhFhfRGIk1SRr1OJ20b/0BXzejqeJZBmaA5i2e5SfHru85YMQzjemabV7K33lWwF2JakzHFrCLKUbSoywbuZtCxRqjA04T4PowR8ry1276ljN7ZKXJMYXl6NnzwbXF/3r89U1q2p9gVJ/sz5KEVdHBJUsPhl5LGHoPbj3pS9++YNv/cnpd76z/5f/0ui1L4z22cBuUyWqgFSgYK3QkgXLRb3lnzticNalkqStU2QWQUFbeA1PvAGjdNCU2P2AxnkqwaxpxXZQIKlba9J0RoMkhso15hh5jUSoKSCI4NrJt+8r0BcFLWEdGDYBiRvbllKXTaoIEa69Ao7ANXZH7bVx65g3VkJDA8qLtJHdJYldyR3L0HdEzVqVDy1VAsl0iFgwtkP36ZN3Zu+9OfvgvfnxhXtiM2B5yN5pjPtRc3E/NkejL+pkgX6orSCOuGIPr4w0ssqaMcQJW1jcLubvnvQfnyxXYzZBo6HrTe4seAhkB+E7h/29HR8DcQPaP7zB7h9OifthZ/qiZIjRFpxwbOPR5c7HSpoFqrKqk8C6al/EwL2SLx4D2ebQOREy6yIYNslm0bfOaR8VTYHQBfVJEH0zuFpOmAqNp2E4x0ZjUNs/g8jBeW29GRvOGC2fRGUbHDJ2cc27JqPzs8EnH/UvL3qza960ZLE6Uze6uAahYaNjDFdYIR5I/RTf29259/oX7v7wzQ9//OajH741+vI3Jg8ezg54WSTIKkGttxMTqhTqVpAcNj+Mu8ucVIna7SIEm9OGbbOnF2uFWm0OIetjLU4YDLaSGpM1RslG44bDpdzyxeq/xpdh5JfjVbiENA5ghBYEkWEcJ4ygEH9K823WIdk+ddnSZOZjI6O8U0kCy9pNRstj4NGh49jSN6oktMWzQ1RvmG4nE47vac50RJiJm7BS9PmT5Xtvrj56b/HRx6vZzYRFIAeD3h3GKqnQVCPWqTHa7tOgD1ldT8TMcKA7MghyYrE0YzdZcoYjDC7YB2LVY5bx6fHt+6zbhHTqMsuXXx/c5YtlfKmPL/3WejQqJSP0rEdDJBWWqufMYcpdn4mz8qzoTAJRq6ajKurHklRUwNPQY85HEqm3vEjFJIlvQqE8B7OBTMWDyZXPZUDcbqlwU/PUG3MQv+NirxvZdGFrcRwMeEiWjFV9gz7fAr+5HbJq7/J69Ozj0flF/+S4f3mWCQlMThuIXuk1cG/xplDFm4pDkkcsxiq9h3e/9OjV73/4zsUP37z3a78+fv3V0e7kFgN4oK4k3oVsgrR2UTcWwEyQuWmmmgmJCiPAzWkDDYl1IPVbNjGD108fUX0bGOyGXvAIl5eS1VMu/K8dsriW+HI5jFI4uVSKZ38lkXPLvyC7Nxx0g7xGoIgJGzPslf9AOxwxPDbwZsgC1zm1aQOQQbFDhzI78ca2cergjUqbq1qRbVitQ0Wthk1BMQV64mWeHb4n+NHb8/feWj4+nj05o+4408646B4vBFB1GeRgaJTHMUcH44GlkWUY0cmgTaRVmBFQqEmzPVzyCNXn82XTx8+XF7Pe408Gh6Plg4dsssYa8d7+7nKX9nC4YtnzLt+Hkd4nN1zBXqV+Rg2LRxlitSmO6rzFigcxsfNvq6fzuAKGDKIt5hi6tT2dYdTHDaio3ADMCC3TrD/HlSQIYbNmmcdK3FUiXclpExSy/YMhNYAPnTKQxIzo4PR6NL1gifbg7HJwhgdeOivoaj5oohd1Ug1tAaMVA8TAacVtbbU9qSzT29997YtfePuDd5/+8Pt3H38wOHtjfO9o5g3F8aHgobSBdkosEUPkTbjnwCrYgEl54QRO4QdK8IXYVqSoqpZ3HLaIDVo6JDUiLmRzUwuxYldLCqU8Sp8vRXFQKUzsaJsgOOt0G9b1rCiaLaGXHJQVYbnJRVsnScUAr9EKuTuX8C62ua7RNwiC8p/6FdQOpLQ1BSnNU+uyTiiNwhDYhnEnVQtAsuztL25H50+WH7y9ePz+4smT24tLUFhPwqhMb4eWB1egFWAPQ16ozyipZkS5UqJThZpXR1o16h01n3aNqgQRO1MvHl/0qLnPz+dvPODDteOL1eJob/jyw+U+cxX94cOHo/EB1TetBnME5NqlMjacjNzkCZ32M7u2MJbDfcNictgGJPqy9pFBdSYQuOM7g4FNIuvaoKZ3ab23D+08BY+WTKBrLtXUP0lMbbOa8I4v3sl9ADgtOaNFM3q8OP1iyNZWzOKwUQdPfZfMBF6MTo/Hl6fDGyCnPbarYvZS16I9sBMQe2DlRC2OlIM1ExzlWorcsfhy4SsPX7v74Mm773zxJz/Z/YVvTh49umXhDV1lMTRFXS3FBLlUi1dx9JSlvRTTc2ql8eIl9BsuYIpfdYOwB5eKV9SqJr8kNJSkVNhSMK0YhzRxTnIJOFJyKl5hv8WToCKki7Qw6zjKQXe0ZBTE3ddpKCJhlloA3KT0i1yBUfE6R4MKBh7hYgdWMcJrNCHFvIi6s0UQ7MIsXwtAjBdEEi8kUwpRZY21I/cIIAqGMaunVzvTy94nH8/fe7d3/PTmkyezBc2E89K2hL6YCmXeIeR1WAYybWr4hauB/NQGsyCxCaX+0KGl28azEcM0TAsGcTE/m95+fLZ4cj2//XD12n5/+sZgd2fAdKRf9L1kmr7Hks4x3gJH2lOWrTJik68suWES84X2B3EThmHQgffhGcfB7D7rWQNppmtKUS9EIfqjbp3Ii1QLesm+51jINrQki0L2cDi2cTILlq/vHFGetpTkgH7A8PZ2yAAQYda139yMp5eDizOWZQ+urvvTa1b2DdyDlKlOHTvmwMGwT5rBckWEkX9N5MUAqcHWvBj5cP/V11/58Hs/OPv+D3b/0q+PXn1tzPAMwzbgp5iq2MIkqjcmXMi2tYNfq34lQVS1qUOcTT0IBonUbTXJzUaN1/ilbTFoqjc+VtAOL2xyKh6ghIP1lXCnjhSNFcBgAAlbkxKwy1FBafmr3zosh0RIsSUkL2Verh1HGWBWs8Fd0JpI3KNDaMIDa6dCqEjQOriarTmUaagcpIqVf2RVRokVAimSbQ5TGkB2leClY2S7IXyXr0ecP59/8C5Pg4Pj5zdPT8kADcSQtxrwDVa5+GDDk5XdUWnqqKxXLpVVEio1pWnF5qsQ1EGrOVMKsPWt+um8//jcDSUG/dlef/neUxqZ4cv3l6OPl6dno707g8O7S1xRrxrwKsbw8J4rWnhK5EtG9PQm7AczZAbcymmLx3vwvsKPBw6YvKAHS88W+cxr9BhQomW02VNzRPNuPg0tOy/SBXQg9tZ1coy1sNiG5s4BJzjwpMcEC/j4Kx3RBW0uX6HqsRSbfQZup8PZdHgz1QlPjwc37tjfd+c1XitxqEj+WgPlVEbzdKXQTFSQdemhFUWNkXcmR1945ejHbz9784cP33539KUvj+8/nI33uanILAVYxkXDeI1i5K4zbwkBG7C53VShxJtLkNCOUqyYIkL0dpR2RiAKf+t8El8Ul7pngi4TBE9ozAUmSfBc6ufGBxSF5dsx997ZZAe6Tg0lFV1GyVWuPOOENM5WSR11I1BXyMpogcEfnLUYg58+gJTGHUHyBJ0Jlf0YNHFxPkXQsYycjrle0aTCKRHJumS5AKee7dxMV48/mL/9w8HJk+XT59MpzY7VaXww6u0ysU4MM8UJHVkMD9hE45Y3ZaFn0iKBoDGguGC4cffC9vXzw0Z8dGLZvzq56vEdsevZ3tV8Ml2MDnYHdw97R/ccfaTxGY52Hz2aPOIDL4xJMlB7wXq3/sGD1eSAnb997ZD7A69lMAqEjDyvUmF4sZ93cLP30mg4m+/c8jUKcsR7j+4dOrid61Ts9EIfljEd/FlUerm3A2Y4F25iv+RLblfT4Xzuz6dHRnNnvZMThl7GtzfcAAYswblhx4ozR0FdWUteceLKaIxg/sv4ZYl4o8WBfqCnTDUUqTEhOaHNe/jwlUevvv34/en33zxkwvDVLwzZ+YKOdOqTVTWHmdX8Rhsol9R8kyrWBYhDkDq8XclE5CgGa4oC1nlNZmr5QBPaakEVfrJZbDaSWm2AUNmtqohkzqO/HQHhGKQAsUkkaToP3c9DJTn5y0NJpiiwQvEJWtF59qkk5JIVH2q/ZdPFCJVaXhuqwarRCeVEUkwdMfHpbQ5NeIe5IXsxRIUnY8LMjSHzGadYp7Bl72J0frp47yfLD34yOT8+f3rCwlBchMo5Ptrhuy0ZYWcQnLfQYdIyHq4JE8KG/hJAGNo5KC8yLkjPiQqIwfBqmjP6pcDhDwH9wtlzaje7PvSXP31y8+RieLS3us+egLvXp5ez2c3Onf2j1145eP1pb28vH3VajO7sDh98oT++Mz07Ho72R4d7I+Y5xnvLmR8X9CukFMLOvpDVRf/gkE0AmLhbsUXq7bTPop9Ldo7a1fVGe0NWxtnj5NnP50Vcj4a0zx5QmIgv7J6dDy6ngzlLYW/zgUXeDLya8APCixa0tc7UU42yxIB2zOLCxuVj5LWOLNnRECTpS+0sYLsQkY9t0H/06HWGZ945/f4PDj/8cPDVLw/vHfl2ZRjYUlkLjeQcKMECcZF/xTYYohsDqUtTU6Iq4CVUQVufwh9ZDXMNN0Ca4ARD36KyM9WUxj2QwsVcpZ7RhgJup5LAsISk6qrRSAq7pAUijWNjSC2Jpkehjr5TQXt3KKmeMWDTyYvmrGhDW2uzQSpmqt6pH8IIJISAJG0IElonJ0Y2SoBMmqiEIs9mcJcv6T39ZP7e2/1nH/XOzy/OrpmSh9Yd3A/50jweRZUClq/v0rdrZa2Jko+cIywnBamcNV06DhscO/x6o93K/Op2BTasqcLLy+spr58fjxcfT3h79/qKdTnLm8n46t0nu48+GO0e3FxezPuLvddemTx8xsjK7eXpeO9g8ujBztED3Hx2dUa7Nrn/Kg+Cw4NDPoyxuJ0Pdg/7O0O+GLG4mi7pPe4fDNn0mi7uZGewf8hUInOftITuVeMmFzw0srH/VW9KG8heAZe8C8/XCYe3N2wlPprPxvPlLg+4jP94L2HESQf2voInc7vjvuM5pU2FA0jeNJH/zVAVpeQJYBF7G0kiDiF8lzc7D48eHBw+f/+D1959Z/DLPzd65ZU+feqUNGRY1kJsbuD0YFfjuJagsJRrpNRFB+jglkP0cNxZcMXWyQTCFfVamjiJmGCwSSJcypgIvCQWSG2KCJwMVDZdw1YW3DIlDy+NZqMX5gGBVqkGtn8+DbM+SUIzEHMXk5KnKqno6pHslZZESu2NFzTMTlHxXzzCokDmJVbfMmSlbCEJSKYrhXMVXIFBFBcELgqtEE0T7z1cXyw+/mD24x/vXD6bn1xcXeNnVrO9O8PBPo90UNAPqu9f29KbpzT5MtGksYPhMkgqFtFUSCIYyh6pJa5ZOThX15QwDBjo5CjPZ5J9cX5rO5zOynx6M5tOr0/O0GjO60793vmHJ/3dd11dTQO+fzC+f3+ye8AEvM+KzOgfPdg52N19+Aj85eXVYHIwfun+6vbmlp1yTk4GD1/COXuX1/2D3Z27D/un5wO2ITw4YI/U1fRqyPgk+T47Y5qB8Z+dOU+z19yCGJza8dstPDWySQA5IU/8sJCZN09UJ56ZzbKWiX0IVgAozkmnGQg4CUDIvcgqgaVJkIsBWjymNHfHLz98+KMPPrh656d3jp8Or676bOiqi2q3UHFNTUSCFTfkuXQhEFLEleRZgPSiNdSOmyW6hgo0YkkVi0AaSSUWRoVJKJLgr0WooMqViZrEmjMIdzst9bdhtiXDoGyTOTnDDQr5yBYru2xNBAQ0yUbrSEqQiddzoeUSpvFaQ5uDeimF6nz6eAGC2MZZNJK2Ba8xO6Q4rFbcHFs4HbC47CxuRmfHlz/90e27P91fTs+fX7E9KEnsRrt/b8SrRT4Q4kBYzXqvNTQsdR5HcRijmbJJ0z5gQEItsQMSr/Uet6WOYXRd1+ViqXVb3qpi6u8wMJX1LmlSbfEuzhcX52SC6nx7cjH4+BmtLH0+FuPwxhV9ysuDg/0vvMHgzOLkrL+zM3n1FQZnbx5/tJheje+/wgvAy4vj1Z2d+YNH4/Pr/tXVYP9gZzla8aHSg93+7p2d6WzoiyOTPb6nS7dz1N/luxIwpjWircMZSm8NUZlFl3iX2SIThKOdF6JktHLW8iZYWwDEn7mK7a0btgyQYs/+4v7Lrwzf++j5W+8efvJ0eH4xvHdvxto4ScAOW2k0sFfh4Zlrg3SX6mpWumgeJZJIfutYuDWUtc7BD1Wjy6hMw2rwLRxVCG1MU14HTHx03/bJGCsnrZBqJGGUKsNWdjzzhx3DINUOfF630d45Etqo1MBp8ArKuSQV90aWS+BBLbINmw6rIKTGPu3ScVSt/DVsSjvVpKJqiD0+KxUaofzTbrij4eXqo/evv/ft0dlTNrS/PL2lRULEZNKfHDKXTbWrHORWFGvoUk7WM8aoCorJ2fBGuLcc4FQ0RamJ//VLWBXKFeGCELt1Qho8E+ryoFLj70lCAjJkxUFvj9lHvo5EmPvigE9EJG15c3XNti58bvf2nEGb2SefTCiyq+cjBjmfnU9Y9s1T3+5w9/n1DiMp85vJmY+JzPCP57e+C++LG6y2W4zzyjyV2E+8oJyPjWkCHQuKXqWFZ47KfPJsZYqmnOMEtJVxOTQnW1n9AwWxFIMWMlvss8MY2HjFAtQHhw/v3Tt+74PXf/re8OeOx688uhkzS1JdSgVZQmWMJhcIINmZwpEQpwYzZKROyOPYIIvV6MI6Tw4dp44OitZmihuVSQrjxBs+euTmmQyplOUv86aC7GIgYJWNrcRYLejJTzBM5kj9w1cZllATnwmD6EU8LN+U6ghy5SSmF5nbJyuNtEESrKgNY4uhoDq29O5A4QAySepZ9tjCDm/VKVlcFCV2mFlJDABEmx2edp4/u/7JD67f/P4jvq90Pru+dowfjF0++Hng8HwqOzCbQc2r1GLq1brFNbBECVf5BA9xiBGCy0ap7txdZYKjcp8HCfT8xGSksLqs+Ds1Fz+MPFXIYc45gq8S4R8T00xPT0hhBIi1M2RqzFcBXWY9Ys9fvpNEjxTiye2CLaUmo53xYMJ2xezMxLOkG/H77jBBFgkNXRyFEjz4+fiXWQQVxOyw59+cRzJR9AISRQA7LlUI3Kjq9hKIWaycQBgSVCbFnNQsxZilBXxn7pVXHz1580fnP/rh/V/75eEXX2eMlCfZsnMbI62CUIGQd/ZpERl2wU2bscbUXFA0jp2uSQ67zkUbj2Q6GQhKx7muEnS1qksRLeF0mw1adcSLwoa8PdXRsrCuRGCqX8ugZEUlsRFZ8fKo2jcOJaxjUNgF094qEhmNTYgsRLMliyTmqeJzeqtrES1QMpM5KJsLR0Y4EaKDaDCInkoDyVuWY4XEef109+qy/8lHZ3/2x6tPHu8c9q6OeSMIV/PTZAeH7JOydCjT7ig2JNOYJh0MMIBERjKA2ORAq8QweCt+gxZKA9WaW89AKYrqZ3oXoHqSBEbCjv/Yyc3irrrtc0YOs29VhpUNZACnYYJQOyhePjy/0khFO54ZeMDydjlki7jh3oQPszBOxEQEX2rRx1is6sCTwyvMZuCMWQbA1r0OZet1MONHIyuGzGCsoqUv+UpUUOxtNtfa5W7l3QfVgacTS1iSQA2QCc7Fp2iRy3g0D6Esu50d3H+4O/zpyVtv3/voo9HZCT1S32chsxGLba043V/olb99mLg5Khy6DTA6dNFiYgG+6LSKiqRPSVlHCQQHW5QUAGYzdIE0VQzzjwXlV/VfSCryOhFCOWqa4HNuvzTFDWp3NLRRX8w6EqrCEBMu8VZCCIVvPSKGpSfRY8oOovAqJIt2w1e1t47YSHaBmaN2JGNFB6zoPStelMorQhCEZzF6SOnecrv99p8ezRmL3zk7590bPISnoB6bFcKEpWS5j2sSj84wBITAC+fAb/gxA2fvNBjUOhrO6IBQZvh4QdUxRX4s5GQCwBUPvg6oq6YRKTaIlpM+ppuFA8WJItXQINPc+u8Z5VCSX/MMhk2ybQsq42+8k+Gn4hlN4d1FG7c0ccYJkYbn6ZZ6Jiikd44Xfrpi2kACmcGzaFSEHypV5qJHZxkV4fA+XTeclv8AEy7zWRXWRxEnaj54v0QnnF9eTnbvPzi6f/z46fy9D4dnz8ez1+bsPaN8LBwra2n/ip0hg1sM4QpioJXU0rZREBqctUKtoEujgoqwTdMkrUm2A+VAKIg+1jevSffamDSB3u0DadlJlDRKVAc1GhLv/lgVbwpYyxPPI7I8P53nYtm5RWIRk1AUqixWyrbuMFY25UhaIXW23UIrULPqGl7cPotebBoa9rD4OAgRZO0zU169xx+e/dmfXr/7/stHfAZidj71gZAqtrdjk8ACLLujVriaqdcps/Qkt5dYQyGipBsmYhxSC1bLQcUd8o2wHXbF5yEtG1ownUeXV39b4XJqk4Yuw0ErvserZzKSGke2C8yqzxoUisHNQZ4z8SPmyPUyN4lxnSdtmH+4H07oi1G0faIyC8hh8weEf5yQM1tkSJb0dDXxOtpRALGRvgckT4PcUQrY3K/uC+bR+uC4KMhGhIhKoKxdEM6BmJpqAzKEcBMcHM7WADq9ttHLq0tW8D186ejp28+v3/3g6NkxU5S3uwd+iVuatQyjoZd1pFTAYCrcp4EvoMumjvDYnJSxTmvCRBQjWNsIAhryOp2AmsVuEuYoysagA3ptCZIYo3bpcgQY2St/LnYwzA+MOCFp1uYXDwBY0iThn0qGv+mcTdhK1FwCuDZtthK3gvL8/EOmRbqF7u0kUU8dRoWRtcNCkBM++vnjZ9/6k9HNdHe0f/2cj1N7d6Kg93ibgbqPQ1i3MAe+mV1GyVq8xKFKUBWq4iUgkIjsKoatIF8Ic3trajw12upLG8iAiEvEnHGjCvvDrMp23ksDMnEHcoAsOgPIj3LRQ/jTP6yvnKm3BLnnErQnqVOpNZ4nrCXa9IGuxwLh+Y5gYYRZnvcgk3FytA5XF1Th1KdkrfJqMcf3dKTSDplA1DC3ojgYY7Xig83NiQDcqzELKwnhw5lUckKA3u+gv8vnGZeL6eX+3SO2wbp4592jx09YqTM6ejhneEbU2FtKDmK5Gm5B5EUAUXkGjyxoWrE3yUXTFeA2ny22WwIMbkWLfAtgJlJSWymxAHRNVqm/jhRDoklXyehrafPLiUBCQSkwNaQGZpK2VsBsQ77FnKRPHS09zbRJDaMuKYESGrx1XtZlv+FmXtfWiMwOe61AmAKNMYVS0FbUFIWvNVxfDz/55Pz73z177+2XJyyQ7j0/n7F1DKg8PB2wHxrvHdl9hJS6pR/mtrQentFGzcuh4UftqLMB67Ki0JSnMHccjW1wrTRCNmIsOUEpqik7xyTHyqYIY3AfbWkMrTclx6zULYE8kBGNKApeo9+ZXTyu+qUmcRgpV0sjKUjhOqe91RBzljyosVXspqhyjOTDm0YkeFbJTM2rKNh0ixyaDTw8VRS0OAtjKS5cpWEGCD5p5atEQ0Ke0STZlAk4PHzyADvemV1e7t5/6e7do+cfPV68897wl85HL90OhnwmQA3CS0mSfOYoY1UC4fxhS9U1C+s8vkC95lParKMI24S3KQKVZ1qtUgIY6FUVFMuBvBYqlKaxmCKrXcIGDTgI7E8+gjpuuUlveFGHGz8RKJO6HSZzlcFKz3kNiLaKLCsYTVpjlhKoO6ZM29EEvWg404rtWpE1hEBo1oRBbfRm0lLYWSxGZ2eL9989/va3hldndx/usRbyYsqLA2Zod8Qnd1m0zC4xNhGZIaz7N9ZG/y1/q7A+FH04gwALIBnIKV1cHp3Wym1zdQgH4glAnZoIur5HTU/rxzmVWm2t9N5bzZU4rQqZn/zBN7A4IpA0fyLBiQv89S9C5XzVMla8JYiMI3NOW1d2VVJnSViIE17mjawlKnuUY0afFa2MZJW2OUMKIvcvYg4Lk0szIkfvALEh+OLGpIhQU44oMx4Pdven5+e7q/m9h/eevPPO9U/euvP0k9HrX2A5AetIMUipiRBJwihn5RTjlKTJyqw063jUiKoRH50kr/xyLUjR5Wzq5thEghkmwTBaicAMxEYGGlSNywjRJz5WiZ0mIIbWJAOesaMXeqHUQIDkuKFz8yvyxrVFyzBGokGEN2sF2NTxErOkciFgXS4NvoVcqIGHbUSEccMNoyL47FnsUJTijYSnwTFvnR4/uXrzu89/9OZdNq+YjG+v5ldz3rqzLuzyljev8LGjJ8Mc2I2c+4YemY8REY8pqLHesPj0bcZjeFjR/WIgDVVhmDFEQbQe/fA9hxkZEukzJeBIjfjhp6VxpAWbadNagl8vwidVDB3Q3CRL1mM107Pps9LTVG0jODv8kalYl3SzGSEIVlraAYibR+oXmqasY8+XUDmNmZWfqUlGydYSmhNpAFe56o3KEMwRpRKqaIwAtgiWc54ec9vRDTjUKnZEHDjylSHPrLtjboLz6fXu3cO9wfDs7bcP3n9/8NWvD48O3edbxdQkl6hU3EotoJWqXgrwJCSHqf4HGEjiSRYXxMphSGQR7Eaf9EYlXnQW2AjDOJSETIeuk7wJJES1Vy/DJVxlg10Qzx76oRWukgoVim5gptNITERy1izbRyuWFzIiCgy5wDo5DOONKbYZGG55DVjitQx51NE4EOlCJlW4Q+KKHiv2ipizCIN3Ji6+8+3Z8dMjVlOOBycXvLpDPsnbap+xUfPNhr8w4YqzxBDNIL5/F5fTacxE+6WwwUEtJVOxcAXqHGakWlPJ8w4RAz6T4c3ezk8tIXq+1H8bW7cxzWtVcGTm4KWb+eSG7XqrGKXust2s0ZwoD3ZK4hmPKfbFfHp5enN+ypLqPm9LUTDMPEx2JvcPd4/u9vu8bIFeKaYIlq1uuWRTuen56fzqmoWjbHtBp9Dx0t3dyf2jCeunXRROFiAma9ixM6laJfOxVgwOQlpU0MrrdPekaJk8I7ZcITcJRCHhQJMqUd6W3GWf1sHs+mr04N7RnTtnT57M33ln+MvPh49eZmsssGHmsaVI4gUDqjCZidZdt7BbiuQgFlVdk0UrQmMQDBG6gyoephWvu4naWMfjCGGX1BJccSreVgLJ4kaN9a04TABzay+GClIN/9dO6JvecrIlLBMqq2WIUJCTQigJ5jKHZB4N0EULGGRINjyDWUjFqMJrdo0uhBv5TWglbrhJVZZLeYxn08HJ09t33j3+4Y8my9nBHV7oXZ1P2ZrPusDOSntsCspuZrQqpDgOg2q8xOTTYLxOq6RtIaVzQvRLbfTMYZT1NAk5qMN845jpOl4R6O35zPP+4va/9Z1vn8yWjG/yDQdLyC1pwrC/+Off+Nq/9uh1tuI313YUuOo5ObSvtbXFdMY+XxSb3148//D6ydPV2cV4zjZldlNzA1D8yfuj2b3D+1/60tGDV2wlW//TG8Di5vTi4w8uP3rCgOTu7YpBpJhKiaj/fDhZPLx772tfPXjpYYxYFYdzeFeF1GyB68/AacYTtRcQZkRpbC177mVUKN5dxOUMcxFeXXfDPoL7OsUOywcmN9dMGA3uHN09/fDk9r0P9o6fjG7eGOzxmeR2s09tlC//CWtB656tfmpyZMgzRiu1Eu2C6tAO+YQ+XExI4hZGEBSWmpo2Sj6RRFZDXeiAsjyjI+aaoKhWEa+aQtMlFMcjiO08AyOQW79GkzZRcb23lhNGoifxW0Tl8yMelXKqCDpWcndDCJGEHOI13I5kDY26IG/uOT6C5oBl47KhEjO0G1BDlhHrHxcj3qB/9vjye9+5ePw+QzJ7+yO+Z3Z9U3tE833q4c5kNJ/OV3fxHH3DhZnMAGIUFwxZefQuKhZuxnldzxBTBVHySIlX+blCG1VG3oe93WFvn81UUGH+4+nsfd7TE9miQO1YCJMvv3q9YKeZEVunaXF+6GBDwVdfghWLi55a2x9OL5+cv/ujwfPzgxlfPwQqOnWjM0H/Lh/+PT45Pj9ffOXi/pe+mZeGIO9Pzz58/u6bg2eXhzz/MkSbhrujUrGjxc3VJ0+enJxefeNrj772FbVpBRnDa2zkJAfqE/c2N1V7QE5foKsiYoKld5d/hompGBPxIUcszsxjIS9Jnl8sZzd7h/tsUX7LLsxPjwesdD26hxvDKFbQcF3VSbgDYDbVyIE20VKzWH85NmqrvYq1g9uhEUAid9Du2gGCYlZCG74hgi/0FJukMipjiiYufQTO61RC1KFABKItFcyzPyHrQ3inTxLojloZrRmKCK4nQBR/RQkD8QZvfi3v0sZzx4zgWknRkgBj05O/CsV8HWqllkwNWQy0F0dE19W8BtjJjUIYaGd+Mzo5W37w0dkPfzCcXt17ie9a8umuW14ghJJasD/iY2Sjq5tMGvu0hUYu1qYXUM2gjMv97IvCl/RoXFZCCfVA66TmfuG4B13bnTSDnF2lUu5UiqNkDFBmyra+fLGM6TzMhs/TYGkKdOHNRnAJw1xS4KOr5x+fv/XdXXYcXLLWWvyIJ72zhwD2eVvtzRbnP373nF1Uv/oLbLUxP3ly8c739s4hRBvdpWnTCIn52LvfW37h9ubj7//4o8XNq9/8ZgzarCmi2QQrgU5wOCEfOI/K8S5vJwRYpBDFISiSVBD1Y+aUaR8mKhVAjvnE6i5bwy1ursd7e3cmk9vnx8tPnvXPr4Yvsagt+wZI5iGv5DUB+SKlpVREt4At0KgvDbg0NWm0W1Klhx94zcKNUbFLmvwjziQkxc6EbcHgHtHixJhBVnIC7aaAWVrUPk4L6/xrbgQipUtN5QSzcCLW7ii5gpf2atlqMiH2SIyrahWN2ccSxuNhIhL2UgYqyiK0aEhNIvWqkgq5o2q0a28rduHWnQQlOwQkZn5zNZpeD55+cvPjN5mZ4DMvd3hxfrW6uuLbEd6TqIh3WLHBNmK3iwkpcreG0THkuYmDsRSXxeiEdeeCL3RrCAFIACZPjk+C6qycay95CYgfN3FmDPFDkESrHDt2UVnKmRWcDLHYvbOcANWP0Xw7AWUZE68vPjl557sHl1O+BswMGvWcHAfDLOeQX+eyKz/F9tb7l+wleHT/+v3v7lxNWVbKWJDPickJyB0hV1XSdXrLV1ezT370ztPR5NHXvmbuTOQfIkTSgmIlLAI6+inR1JS4yKbGMkmTXwpYHFmBgIl5S4RuRu5NIPiIi6ctFtfn48OjvTt3bi4uFk+yfo1tNSZjXv7P4yiylFeMWqDUa3pUigiVqjhCKo9W0c0bZRQWi/9Qil2RQHIqJEvNzHp0PImakSQJC1ymGdUkuib1Vq2Lk0Io4FCJ26lESnGzR5qESo2+oknmuIWVR9cHJFuTir1AKy9/BkxIkleAm9qWQoJGRkEBtWPXAYsmdagYAUBWkws76ZUR2Tm3Ai5IhCsijEdsb8aXup58cvmjt1fHz+/vDHb2R2yofX3jfZiDgZjdXbqBvNTGDmalGkXFF1MY6cBwecxjCNX+KRZzvwh1h7j9KopeyVEpBV/mvhjb4IHQ+kccb/e1PJD0mZiy8liKalxbQVHFt5bgC0EhKhnnwepmdvb+j8eX0/FyOOYzoSkvKrtUMWkCddJQHOh3sJhfv/X27f67w/nVkE2U/KYptJUB1d6ihcqJUsZ28bOXlvOPf/LO2f27R/fum3c5uqbfGplHuRACrSpEwFtCosGhVSzmZBlwRc1+LIB153xZGzXihzjhDqNWgzlLZwaL8d2jwcXF8ukT9tUfzm5HvlSG1nV01i6ja/kI9VIoZZCGnVRVNFXrl7peSCoCQsTW4TWlgeS7NH4BXhTSdGIJZFMfLvBFohKS7gnMhgw+GgKgprUEO6WFYV8EhCTEIcWwwrcVMxl2j5mbzkpRVrTwJG0rhk4BoxxWMFl5eH6xfnVQ4SKEoSEOwpCGKDewoAgB1TqT2wIkTS4UEZWnkAnf/Tw7Zjensx//8GA2vX+XXVgGt2ezKXv/xUa0Tz4Qso019actWeGGS0dUjlZvPJDnOFtN6qZTe90PGwJPnknSFN1BBaYmr1eExZcY9BGj/ZfFkoPkjEFEs7HOQkuHANYxlTZbXJ1+2Ds92+GNP1ff4JSWZaTWOcEiqWAkgLN3e8uWa/Read9HbqG9ploHSv+Wi9wvyMPy7vTm+O13Dn75gJV4KQfwGfextrXGgWxpb4TEomquKaNY5a0UxKlBAocAedI/3XSH9ye4YRFlLmdnxNjM9OJyNZ8N7x1OPhktn5/2Ts4G19PhXRdRIAbSHOi57WlN7XVii3fY0KHKGqkCUS4UQfO0xnjBhslKY10X8LZ0kVL+zbU6d6q7VqTEiQrLc0pNNCP+AtEn/QXCpctrdAfTLkdVQyCxPm2FSPlZdXKoW4QaqySZJc1o6W6J8c89ybogPBikVlgRWz8ERZaJiG6pREoB2YRz+leIQkgxZAm1i0X7T59ev/OT24/evzte7tyhwRncTBc33qpE3mWpM6/73OBbVXNqkZpdULOFwBv2N4InLgBwoE9Ww6i5ooOK1Q9dCFEpqcNZlklVxHLW0dSh8kN5gcbRXIEQuzBZK2Og5CBUxbekkHkmI559uD8d8tmnoQvbNoLXGsi106YLBEK/mg9Zz+ejWRYAbWhD0U7F0EhXUgOeDwefnF6enKRpjMrMrbo5YvgUKugwNDfYK85mkZX8qg9VZwJpmVYCbzCtHKIGNUZjV/IRX/udL/jI9sHe+M7+6uJidXrWv75h32HbQWtQ44vk3HYlFkhpVT0ExaMuUcvqZMmkdJQUFhKGNriqA1WwolCYlPIJBrgmMdAEwaQEmMcSI5+w168asECAgaiKP0lzlOSCChZeWhr2AJLnEoKVzQYFHC6hKSvnrHFKw0hqzIqzugmNZq7Waa5fcsI4p7JJzt5pO5s2BLPvv4fK1p8RVUqKgfFiNj49Xn704fmP3xpdnh/dYQkUXrS85pstQYVih7d6+LTJLVWDvmfuA7T3rrO29evjfjduFaOa+Knu1/dckHZfUjB/JZ+6aTfSBdW5waNo7Ig/8mBYaNUOdHmIvrROoBkMIzNVHAMgvOJbD1fMq4z5HgubFzp0FgTFbh8F3D5XaoqGFt49SEmtH0nbgcJskJhdjXZns7Onx0mLqvPlipUP7vCd+iQKNxCoNqqGa7Igs2acxgFL0wvhYFERYdbP83CI5WM0+ilMvTBl3xuNx0d38cblyfP+1cVgPnNrRk3WdXzgm4ykBxFNBDCc5i271fXUjiiw1mGrCkeh7VPy0ADy6OK5Ju9JlJeZEsGTeCoQfE/+i87VXwKpMWnAOgjgQg2Xjies5MTRIIoUo/Xmo0Gni9dUW8XVr9DDG8jaZLFaKlhoZS6hb/rYTPCr+rTmE7QI81Tgtf+X4vFiNCtnRu9oLueOjhvyhL3Gjj+5+eCtm3d+fDSe7dEMMgNxu5g6J06uqDurHSYSqAkze6dqTk8Kf0i303mKG11OVGxYDWB1R61FZZnoTlQrsWO8DYRfNdzd8S0hctYdVH0mB+Gk1rLLISAwIbDWNP6EFc+Eg3bNXoM3boz9YmeycQjB9qlYfC7ks0nbaISbzQu6w/bap2wbjnoYzGHj+c0te9j47ReBoKcck7mqjmaECte8FHFYCUj9SEzXBq8jqzizGwXgXPZInZefzReMmtF12KMZni+eH/cuznksTN87FlJkZYGzvzgWAcrQX9U9sWRbF85rEiEcm7KpOOfGr3Arsk57MUDJNt4Nnry1Vr5YK6/ZJKwMawJOVoHExCmpAVXa50DEa68yScGB9uHBuatMQMGLZknHJ1ruURWJ+lkEhkGwSdh0zzpTvYAjy+JbVCWUcGXfurpVywNPu5jk0XIxvjhbPfnk+p23xs+e3L3TH7GDEw3hdMagDHojkqZqh7ETOnq0LdQJ+wDVHY1HUJo31BJfv1MNhnLKA20koxFZ0nc2yuPKjqZO2DSRj91nVEZlQYA57CKVc7OFSZWpvBcVtMp0qlXJEJkUXnrl8w9zP0noLGKhBeMfe1obVOH/kQ+p1pS+7Te94eMWQ24u5Hg+v72mpZqMd657s52sVAc7/DmbNQtGWWYuYbNgd1umhKmr+CGZZwKDzR/xQ76T4wAp6+/4TFZ/zpQ9BJNdjLl4drw6Phnc3PJp4zCRo7zlpmUTVZ7VOEcCLSymlU/s9i9OpZqQULRtVXaTbKghgFUkQgq+ASSunO722RwqGJ0y4RDdS//Gp1A5G8Br/JkTApBHO+pkwpknTI1L5uskypYTqoS1desoWgBawSSav1zLZhYVRPJBRXHWGZWJ0eLn1RCI2g26MAgGt2dzz19SixWl71aZZ89mH394ywZe/eneXZ77dR7GZPiAGOUJO5yQ9Wu4IA2YjNM+N78iZ1hj6qeyHZaDOS0hLSiVhz6q1PnFVqUtEHpqPK05P8FymWoJq7EnLZXQBxuzXEdynaAVIFmrehbWmkuzqBDKLRc3M6bXWWa6Rd4F/2Nfi8lak8+hT56FozgvefFmsqXLrORieXF+ts/0OStud2isMFOML0vLpvleDNoyIrySAFBr0kjmcxpsu7iklWMvDvLIfXA04OsfNzfXjFa7Y9VoNDt/3nv+jMZwcP+ICtYYlmEwDVXWciJuzU01MJT8oO460EqsAZplG14sHIo6VQ4kbpw7Pg1nndsmxSJSltVULRo4eqiYcU+Vhh1EbZCW1FIlNjXJITFaOPSkJEqG5RRnBWChiOMv93QbIoECpM2BaTgSbbCgFY5FmMchxdsQdfwkSYsjXgTBOdLCrEPsDKYISEXmj00NJzxXPH82++j94ZPHd+4uB/sp43nvdup6FtEcPsVTBnx+EyXwndbh1NkYgxn64vytUxR5JqxF27R7FI20HpieOkDMMxeZuOYFPbm181hIrUrmVd28JSdF61m/rHRu+RqtDkFdjsNMGYj1lUQCWKNQi7Sj+idcFb71K+zi8/mUdj1DwMnHYhbDcdhhGF6d354/P11Mp3yX2FphYdSvwiErT2suWqpGAZzTDkUwaf2AsQ0yaMBtDBnC7U1Z1Yu4yWjC5zqm1ys23r84HcxYGRtVtQAiOlPIlXCEru0mYrA3RurMZkLRhFshBreLd6UC21b3NlyCg67+Ci8tAuDCyTmEWywjzvhaUSHEPAKuazRreEE2TTQv2bkEMV1eu7oVPchceKMW15yglRyFAEgVAsPRMcb0lJSGWinQl5REZRK3VnAhpEEB3qVHinhhUzG2+OMLsqer46fzD9/bXx1P7qXCgzJbTW/qiUR0K5STVakp1HIavdS0OKGN3oruaLkcCswZ1MjbBakwUT4y8UCOeJF3+Kx04zU+XQwlK8ck5624oFam0kvq6jnrSnzSq0wlK+EZcoFpuctiYdry29j9J7wgb23NbVZNdWWjbKnBkgLGTpbLq4sLhk+Y2/EOBQMfDgmldHRLsLlDQWrJGbWKyAcBpDMn72A0XAEzYMtcBUm+88UHF+if+lUplzLt7I74APDZee/q0p35SVNVOXqqQ5YAKICEWhRQhYJIUPmNKrFQffbUiDriiCtRXc2UJop0OmyqfBjLoeMSxE7T3DywjcB4TAKJBwWYrE2qUCSiNLYV4GQ9nLtcFJIkOcBQfNoICOoAglVQ0EBhKM/Xb1RRsDy9RETKWWgqXLszBNVaXrI9Rw+J6gh9BaN5OI74jsLF6eLJ48GzD/YOb3w3AgdjCGDau57VYIIkfIqBinBLJ1Kp6Yfb1OCKYcVdnmcT9EUB3GvuiPqA151w3eoWoQuv54JLZzMdTcwMM75WzYdd2lqQtWpVWStaeTdzEWT2yHCGK7byU7ienfPg7QiquBUZms/F2uD/xw79TJYIwj/88gTNFAFeUmTV34CHxAUDXM62a4Q0awRcog0F7RvA/Lhv1WGUJBXXb7GwnXwyRB3L2AzzkDx89+nOD+bsuYoN8Uk+sMFHdE5OV3wI8WaWdlMaWVY14mKMnoQXp/TrUE47FBOKkh7yqGGoJTTULaqO2mvAa5JQ5ZmtqUDG1n5o8aBKkDFN2BNBUEVKYklNWBTT6rCq1dEUg9abizFXvYjLSfYcxR4AgaQZMpzDHhvWt+uW6u3ZNNST0F8Kr8ilF5g7BbmoTCieX3xOLSDMXZdk4NQIKmbdeIshLMrpWXI14o35k9PlJx+wBeeYXQzB83FutbypvV5UjoMyd/baL9HyRzcpPSUwaQ+B3fLViChubtzsk62i+bwKz4/x9ChFziQ2e3nhhO/YeiO378hReU0IV9Z0ldfKWTNFJVfBrREEerQCpYfd393d58l1XUqVvnVurLelbqX+Y4Jl6c8iYE5uON52fOHS96a4PzKaghOyCGJ2c3bmp0i1WxYYmdnYAp/UHNz1avohnC00jtQnMK0ahaa9MKv1xKIlg7jgLN918wNTPLIvKcrT497VKR/JKNMXR3Oq0PAt6UnIqcRt4i3UyScauui8xmog450rrdM2AViDiCa5imjV01e6o4U6QLFNfVFeRl/qmS7eYh1qOfHCnz9ONejfKcqUl0AMWYLDHcD2EReCnWlySgWSXwJC1SKU8R3ja/4diUUXlLoUn2LWsKsSJsOwXuOaWhE8czm+uug/e9J7+sF4dIbgHuMrTDYsV/NbvxwNXW7dVGwrmd7jzby35LMo+dalWnDXJYorsYyG271LO5jJSIu663psZEMDt8jNjZFKBdGS9zNudmehcSEchzi5tST/G7W1TeUBRVKIyQIKtqwkvU68vXTn4Jz9o5gnzChSlwbqhk+ARVzpG2Ed/j/xWgwlLLPcDpYT3mqg08BBohtG9a9ns6vzyyOMgrbccbADSRSLATsGaEXPgcfu1JncpWwnoyrntaM665jOJx9V5Ri68cccIJ8F8BGRnfh7jMr0To4H52dD3HU8hrjlWAIOCBFK7QqHgv2ssygoSiVo1eUFRFLaUazCdhsi2AxbgzyqXOtJwzZGk1WFUKemT2DR0VP9Vyqx/DyZ0tISErL2WxNsCRvP4IayUIvw8/Kv20oVjTm3gXWAQBoHc+2/8qimdZNQPEEJTWy/hmJz6L2Hn4R1C6IErMOemSNfLFkvunz6cf/qk+EOjdJgxWQvX9RjMBwXifSidilZZU0taSdXNmS0hNQkfr51pAcqi3Hzmx5fkbmZ88kw5QJZznk6YlpRq0PPtwdRHKVn0/kSf63sS8wBiroZidrJwCaSDAQz2ZBCQvJc5Irb4XNLd/ZwiUyRFBfqOoHGMVSfOv1jkj6F+alo2VizrMb9w6O7kZI6wG6ObNDN19ZYdlSPgpxBrJbNskNxDbhixKuW6GqgKvZkJ6laiqmh6XR5dZ2xGZNyV6SvwaM1VvbVE3I4OD/vn5z2L6Z8oJvBYTRLrhTDz/wDQwGjLxzqwv/2ITZ4jUVLCXDbihZWo1OEv+LlLYHMWgLWVjLJFyy9VRdQzNINIdYGcWTU0ev8YVjamtT0UUBidREeCcEuJu2OvkYI/jpmgP+Ij8wWi+mDSpKMEAQsdgt6RxQG9qxbPY0KxTCU4R/2JaOEhLMsZEuCdoP95PZmcPJ89vxpf3aBR82eDp9/woLJ3oN9BthwGdCUx5EFmywUjW54N9/y5Gexk9gnzCgLd+YVbpnFxtyIyYeLW2hE2RWKH0ozi4zH6n7mF1T7pRp5ozpQE5LMReY5cillLHfBxnLZoCRE4s7O7ksvTU8vfF0fYa4gaRkpbv+Jz3BTdMfUwDW7ld4/2Lv7IGWnHWigmDnQIG6nSoYxH91Rnxz9leloGzEI4ytsSFxqibmloBbTb/mCzezsbHj/noVo7qFnlQTuzbfGccLs0HNx0T+74EvdfJ17tDhwyxl8IMzgEjrM7aH2FTIb62AHCgpGS6tOcnlCcSqcLRKrSRfdBMiszyok5pxbAqkI8+zflu+BBMD7mOdcuBURMcGTDzahjP5CikdTxoYF7PwBottGDlXX//AoVutIAkVcWCI1YCnYiAtnnRoVxeRQPUyTnyMkFAW3mrrH0tLEakW+OatVVCX38GIEk/0qV8d8a+kJ67xOPhj85J3le8fzWx7w+PRlPjZIJio7jDZgRWDYAFoqVY+RGIyMKHpEfKz+uj+7HCzTJDL3wIfLpnaU0JK3iIasoJSh6uZ9Bn2RBS0MwTKXlgLWrM1yPgi0XFbe11mIRcmC7wXqqe2oW6qRzjj9wf7LL8/396bej7dRG8V/4statpxQhRV7l7uDw1df7e/wjSTcgzKxGzAa890YpkuSEc7c2fA6/NAm0TrjTwPydiDWJJqiA2gNrCgk9DVms/Or+Tn3StqT1Mnq+2OqmvzACVkhwCq584v+xfVgejPgodyarxvkGaC4e06FVFp3RFYX2VxTIERTnzdZ7modZe/PGuGhmPzIWjV66l9HUo2GW5CNVG4tbw4ZyEfcnMynDD2DSkCjBc9L06vwZQCoPC/3NxEa1OBag4oEsn2qvFYmKxwG6hIrdrpVLfUWG0j0UDuNUncVIqEqhE01LWkyw5moxf6Pl4vh5fns5OnZs6fvfXD2w7duHp/O2Edtl92P6Hk27vLgDk1LaIbaMyHlvlrx+Wce/mkP6U7SvbTTwW4whPkANiM1vWmvP/Nd3/RLsr4tWrraFISsa8ZLWfPIO8O0VpX9slAygc4Fq8w2I1rsIplUyF24i5lE+3uwu//66xcOIdFCZ7SsjGBy49sAXtbEBNa/rXSDRdjOuQc0VjwdHw8H40d3d9kggwdt7sNKAMU9q5hf1XSA7EjQh/f5ON4Iip01Sm95S3c0OliA61908dbKDOHi9uzihlFWnVPuNI18ltX3qRkHw+HpjfJxxdnNkhXkfHDq6np4m9lCc5N/lZCzMY926QIltIsVgnSEFJej4hG/AYpT9/YSJEk8X7WDq0zVjnRyqztV1ECOgpTLkYSzEW6HhEbiiiFMZWnAxrdTUcQMxHPj46ewyOsyQJMJdWtaBLYyLK0VRW5g7RHWml8m7RmiMTSp/ouwkahT/ZBg3stXOxlKi9rgJIUFK7Pe9Gpx8vzjtx+/85Nn15c3R6vlQ96W8IGep8XoU5w4p4EHBBeMoRNeZwiHURxagTSAWM4JKj6FVGtmHHqp24NNQT0T+jLA3KEEeru0F7MF+yfV19ZQVANwrkI2UJbw7AEGh0ZNYA0wlmJpYKP8r46++HL/4RErSsK6qDcoL4bWqQTqJ4f81ohGCxQFGgkNwTmrFe7u3P/Cl2yL7CSGjtJjT3LWrDFkkoK39aPVoinjWRkrOkiTW/vNfM47EKRyVGW1zCMNgcnu8vr64uT0dnYrJw6qBW9Q2IvAh+nf0rFgj6wdXHlx+oyRtsHz58PrqU/h0mOy4qPiofeUSrSxppB2K4dGAlEMqlPCoLTDKiazMA9/OBZT85AjqIaKjyVXFOHZsqocDVX1PKpGnFQylCcnYxxUDX2kYtGq06uBklQ99aIwXWB0qsxYgRUp07DfyFGgnJrUxgJI/cJJNUpqg5tD2fsvhuIaUqQ2IBfJ0L5Z2Xruugo+s3R1dX78dHwxfdBfPuj3D3lRgi/SWgfMsEpJS9WSuFhT8DNmnq+GPT/Pzo+VxANeqXGh8pTRUbNB5aJ3pRMufJOBqQ1+fLJ6NmNBJZWQ5jHdURyVbaPkXMqXpnBQsvJijwSiB3hmmZq+PiqxEJNNU+TD6+cPv/7li92h6wj4zoX2lW0oO6o1m08HCvNTaEY1Rw4YYiWGk6d745e/9OXRnXvyptEVi5yT6PY5+6x8p4PKDzcrM2GCagxTZEv20WJ+j/uT6ledjzWIQ5Kmf355fXZyikB3C5cKa2O5GXsM6JYQct6Z8Lb18tSxmQGL1/yyd6sYwVgrrvZGlJdzWb1BC47y/tCmcMVcH9BJwp/VL3zUaf1rJALCzQQiHNok0EIBFv5WzMhqPERNVRUtbiEkLCorSF2zkl9JCG9O7Bqd1o7um5XI8tA+CUb3ppcizYFZDdwRUQWJ7y9sGi/i6iJ+nRuhGQBShNwSiXZjVWYvFtJI5tw+X8uiECoyt9Wry9n5JZ96eX3Ze7jqHfXdbwnvWPAhtJZjdcrNHXo6o3KjfjF0OrumItCWMUyu/re0b4yY8nAICi1DeqlT+lk87/Czt8VdmrpnOwkBt31+9F79lrw5w7ixb8yhVI/kzFxV1DO5CHgLJD8OzmWJIrCnt3vv7t2vvXHMsiDrcjVSYBYt520m4fFPOCFCRSNLpW97/ZPd/uHrL+/TEXUvpkoqS3Pm3eDh/h6bo+24MAiPYgCGidnWHa3yYUvF6fTkvOf7EDGNjAlwTq/VsZzlzfk5X6ZzMyu7JCQt5rzZuVjwGWPXBgDkNxmN93dYW4j7lQt5fuFAPQ5Yt1MsV8CkJL42ShI41a8QpCyzVWl1qVG4MJOJCIgg8UqE1/xxIoexEsrEBUCN3MIs/coJyKEsOq2C4I0ngFT5slvnUDDn08rl09Qc/hQqfvszXkd3Jdaxb2lrx08+GvoLdCHwIU3SaGMtJebZjG9Uhjxw6kdHEI4Mj/DF2dXF2c2zp0eX1/f9CpIT+pZo8gxZTfGva25bZiZDl0/hb3w8fsjcoPh95+ptBny9ST9m2Uy6o4zN7DIV4lIzNeM1wejHyfuKNqQ4PKCSdU7WQUPrQyIOcXTVXBNN7pPWOHRogYG6PHzjtdvLy+fvfPQSn46UTWUPFhueQX5RYkCfPZW80pxByQs2AX35/tHrb2QNenjLtqmoDwwGOxOmQjPMyDSDG7iSabyLxpD1etzzVqwsvb44uzOlF0G+43uUgmZJSwiT28XVxeltb76zy6gPS4zEmbHVD7XNj9dQdHFCfP3u0Xx2y2iXT1U8fHrjtRLaIESv5FqztNyamD9tW7CydUvvLLAVJV07Up4kWrktWEukOxrPipJIsuVcP2MK9FyHAVOrgiQmQtITiHcFUqgtiQi5EiOA1Bm5G8vDk+WwzpWI+UXTbXWb2iSbXmeZaqMCBLo+KY6DM+mVMQV1vw1eUMLJ20xanaKVVN6D1WJwc724eL549mSPjS1ouJgKZpjEJdpLv4iSXg4eGCe02qecNTDsGBm9mi1vGCagGUQET4J0Phf2NjPIZz8NNFrKKV3S7HImgn1U4HosAfWwPfT5OeVa+tuFSFKyKSzCzWgiTleiVB0xQjOp3LpjKzwYPvj6V0ePHp5kQSsISdtC6Gi66zppHagUxQBKL9SsnaLFw6MHb3y9v8NXRskWKcUdrJhFdN4z8ruj5okV8ayxxhXrsZCGUXarWx6Lb255ObCiGkY3yw9rgnNze352ytq0nX2+AcNOGpgPz2WPA26CLo7TRFDx3e6Dw+HOznw6XZyd9S4vBi7TkYGpzX4EBeSotC68Bm75TPIDmj+LKTWT05qHLDr7N2jukQ0W4XXyHFXkFnYd26jXlArjTi/NqWg1K9JUBmLWec7eD2QmAhEP8bP3QiKeokqRF6K+++IRGSRK68VzeXg8PTfVJIMhcYS0MNEX+akIv4bWhaGhSH3Qc1TSuxefbJ8PmPw9P+ufnvLxZ8qKwU46mFP6k4xpcieOEvHA+GG6vcz0WQEYkXe4zrFxHw4Z+BjS/LEWlJ5pSgkBepfOdnnLEIxTtoShov8Wb7QSV342btQp7ths8vCp3HVW3jJ8cppsQhGpDaIFDGrJQX+y+9IvfnPx0tEp2W4O3EkQ6VPH2qQV4Fw/0IRQUHjGGXOcD3YffO1rg4P95n4kWoINzas3F+5o/fEOmxhjOPqitxmeyRgppQH6bHl1fsHqBp+V7UTIhJKyrBxH1VFXV9Or89ODnd0JixB49xLQbHF1dUXy2IVxLtKy0eMFKvq9DEefPl8++YQJwwFTQypdd1SrVkSWlmUfBVb2cg6QU66mcYCeehX7Wnm65A6pA5RnKC9EoTVHxd8ohy1zY9/Rg6GO4uU/F4kagvDuUPdwqNwIFtRiCRFrg/vFQWCRq7s3kDLAmuVWIFk1GfNTM8F/Eb0xqiw1qwSt46EqhkvnLltmRihpNEI6CcdiyMKz6fXy/LLP1jJ+mlOv0LWc+1VykXGOH6bL4ZS3vFBOj2Kck0rlAjcNiw3hnLlKNLefSfmzBueSl7+ti/hVNaE4sM1m/Wpcz6daebccgqnC63hFOIMC0CfTKrBAupIKVmpIQkkLn3DGVV7+pW/O7h+ejtiIw0Gaz7AH8qkfjCKyMVQtfuTrgsDR7ktf+ebw6J61h94PTiZuUCwGbZgf73oNeVbDkJjBUWLs62NhmjiK4mZ6dv6cR9aFu1fQ1EBK3z09SZ6h8UwGtC7Y+/X67sGd0f6hb1RwK53enl9fMESzt7M33OUuABW2dF6Sp/PbTz5eXJ7zaA9C1W11MRfaOYGc1LcLmOqRDBewy0LlJZpJQDYsarOSX4o9pZIxA6OOHXikJhtoVT9BVUFAndpZFP4iPEo0BO/TADu4dByeE7JaJtDB1U0QldYrh3ez4JQOyXDklGHkjEW6Gtgxhp9NLBc5GyoJzTypZgVpSKnA2FbWXeWEUuL8FRPPYYgFoRhSCXBCPohNe8jC4PgVZ5qyzD2JQ07opvIjbBZyo0YIbPG7a34MdfJ6wFWfqsL9nrw4B0hl0OVo8WgJB3xhO/NV9X1PBEGrB1KVuSV4V5B/HbmipiYBipleOCrX1OS6wVn7VU2FPDwnVNEGtfIrgE+53bnz8i/+3OzhwbPJauarHJ9iHybtBAt+a4SKoqq3jzMGgI92XvrGz40evCSKP1u8kDTZYRNyh53mbttLMvsy3dzyY0TYJk5PW/HZXb50Ye+dW5r26ErfUqHZ5NGxd/38OYHDe0duBYK3Mz10fnp+dcl6IJYG+TEMDqXh7yyRm15fXvjd09GEERufPExeK7YOCNWG6h9jRN8Ai1sQJFyTiGqxCAhck3QI1q/yPUICc5jOn67PL3TSxEvbOJ/heAoeU9zElCwXsRMqeoMbXvAF3KU0mlTddR7KMNAQiJMUfqknb6gjuMSEvWxlGt6GxIkPJEGItyLgMjXbOk6y4Z1pfRMKlTdVyDRbEVrD+TFGOWct4uLyqj+9gkVcQqYGYhsKh34PLzZxdvQAEleguTYZuaBNe0sXfl6tbs57txf2tkhhwIY1xvDhwFfwt4vV6oLB91RS4Btv1xXtmpoNmHokQxayi7TNdx2A+XVHLII06mKIu913NEbRiLwmTjAs4TI8wg+/MX148Akf1GA0v3kz+CWgJK3PnUhTdTK0vcSbD3cffOMbo/tMqUZiuZ/BiqZCl/wqMRpKxsUc0LzhxULWyrpIzac17ifL6RmvG95oeUqE53ErIqxM6vnCLouPbk+OH++P6Irecx4STte3n7A97PwmW83UdjwRSoPMlXc3BhO+2oTPV+GbpgVIVK3K6jpvCGsZNpE8VOltpQtOlMT6FQvpuiMIihHmqSiIlFCi9esARagnBFXO+Zeu+0+gncyE8JbsRUpO/FdBmN6Q7Jqkd5Z8AzT7/NQuROUViReZjMNmLUbe7a6waRVL34gOJ6XXLaS7kYR91EpSi4aDupYCnOkx91lTfH3FoBsBuSS58sQ9msYQdXENPJCf93EfV/wMDCNx1ChG/Xi6sxPL9ODtanplbQFD52RhP6/lmiNf/Z3yxZW5a7RTib3VAyyfp6elEzqFBik/Ds54lwomSjwBdYejpWu8XYGW4oCCIVZlYgtQnDyDsxoe3vniL/z8/OHR4/GKOc5UXtKK/FP1r/Hkgtq2gey7w6oYPPAhExIqlPEYJJYMRCRQdF1BUKos+sNkS1a9MC/PtmiYzGl67knzs7NnU8cxaV5dFujMO6aijuSBG843z56dPfv4aG9vfHjoaOqS0bQn737y4TVdlsoURoU7YczIuKjjbefzk+e9y8v+jC0PktTlsAzEeXOghjbLJeqHq3lI5a0s2Vh1eGBvGyqU1t92FA/PFtPmINaVTcep2atwrM1yWXMK9TpW8BZteEUACYzXhArJsrDOnVodKr0ikuD6V0OQKNe8lpSWlkvLA2ETvDQEgml4BARTsqZ8tFxXihB1TGUgGtWeKYUZpcUWKGxqX7evYoDre0dmHpCevejds4525zNJPZaE4Jx40TUDpAzn8EJFb3Hjm6X2YJHOsyLjMOhHmPKn7l7kM/SpsBLW3R521B0cUuneuKzU1uYqCJuXOmLfCsaq4LfcbjJdNyyQAOFWVSCxBLC6yrasyDZWd770iz8/eO3ex2OWG1RvbV2JOiollvV0dBrtc5qce3v38cAHL/tKrh29NUqJCb4MikmpyxOh6x+ouIvbKx7t5owgM72DdVlTenl1fHpMxzTC+LgOGYEaJ2RaIn3Rm9Xzx49vbm+ODo/4UDblzgqjn7z7zlunTy8oKV5sMVOcmO1IlYCQrsj1dHV24ugoL7NYjFbSNHEap2pSlNTcKt1saoZhF0CsSKhlBqLkzkuwkpTU4mzT0nAbgtlfF54KqIW6NE0kbk2MgQ1DcWRV58CTukEx1OketrKWuxHzQMvRHXAuas5oXqTaoKEGUgnJWMsh5Ejgr4iLh3FZFHonwbSGRkIUowyxc6ElKVwkECMHe/SywmV5fbk4PWPK3jKIRmBSC5g9p3Eq/6GxSgWn6Ja81bs3Ge/PF/ks2epKJ8QnlQF5Gi+lzhY0bOhPnfDxkFR89bK34GkG4TgheSAEW2sHtO7QlgpEcqkIwByok9Ujlq0UUKw1LctFABY/TuV+RSdlwCWtMHOW63K4t/OFn/vG0/E7Tz94+uBmuO/bx+sj3IwhxuyzQvaCtuz+4YOvfm149349JeuE0pSUyklTLoysEAgiczv7+27yz/1oxtZZNE3usjpGuWWPnYLP6EXIJTnWM6MIvRHWH/FG/vPzj599wHbbfEERutXt9ccff/xnH/zko9XNPeJsAIxn8nGYWEy5zNJiVp7Cry7pn/RnN75Cxi0A97Wikxv/UpcMlOFi0woC6cAE6qBCdaFoKk5qZ4GjcRnDQrB8UhhQpe4IawxIMllNhDR6cl8JXdGFPCKDUWhIKO+tKMnRAUrgMotWBI05Xpw6XbIJl4wgqhDE4HEKvLSBRMU0FFyLb2kqshVRinQ4zOUmSYbEC1SkoJlpq7FHnUteBPP0zt7pNIOXF72Tk8wwNCSo9D26OTJkrRlTF5JgSwAs1+DhZv92tnfLnhT9816fsZl9OnipOOVG6OpD6qytK4OQKsGgK7dt+rFqVtzSDKaIomalqDA/ZPn1eHOgWPNPxbXg6gBm93Wdswpp1mQPtPoRl4kxzkWuOqHFaqO9R1//+ulk8vSnH96f9g5SAqS10lGAHsgy9PPhavLy3Xtf+vrg8E7zPbnxayLDv9QIA2lz0FDRDh6k98A2qG7WO5vQzvl25pBp2WfHT3hGLJUGbgSiF+XH5A8P7fOPP3z7/Or8y3cejCcHDHCdnT/7/k+/9y6fOlX86s54ssukBdtG+ogQZVyttOQ9Sh74e9dXPFXy8O9jRKmTs5jqrkW1jYftmDay4vArr6vkgqRGk7RFU5TB7ywREGQbxkIiRFhY5bQuj5RRoaOVmkUHrx3l1rUZvJiW8Q1H8wJ6RuJIZgqun6BtjmUOgQkpi4A/6kfBZOMB7wpAT+aJdHZrlRNQ9FbzxkTVi6wJ0OQMVlIdtSB/NFkjZhVurhYXJ/3Lk/Ewe1CESAl0chyYoUPJ8I0NF60ZLDN3545MvBWwe3u7E3bnflTMwRu4g1bkaEA1A5I6JZyhVO72d6IJ/M1ncptWEYV0ssCSBz9hrfNuWa1qjVmsw5qmuSIwtwjhCK5XnBoWF/iQ+yrRMrQ5iaZJHY7ufuUNNrB+8pP3+CD4od/WtkmsHwKu+/3TYX//tZfufelrfecDt7ugKeSNrLV20aTK11JL9pjcWzCGzOMyC4kWGeAaX5+cH589r/45xNkUI+ahA+G46PLy2bMPjh+jxuHeAV67OD87Pv3k/ZNT7n3kivUB9/f2fVB0LVtnDL9OyOc3+JjWDU7Yv50OmcZ19siSFUm7UVuiLTEe9er+RrLagqOFPIdlBTrulbWkphgbVwEe5cM5F2YYac4qrOAUpuwjQccv09G1WBexhEW8VqaiaGLAkylxjOJlccCoEmgJHdRq6d5BpCoMip8shxZGWsJ/uRU8iUoQyaEthYHiHwH5tHN4Esb6oiQlqHCDLABOdTVQWNQI1lPzCbTlJfvKPB4vzse7/fmFA6FBlRmjA7wgQ+WBNz8e6hhW4U0HRgx4thmOh9zHd1Z2Ly8dLeyxDyZ+WNnE2OnHon+tg+Opz+z59Nhb7tuO1HOP9khndWULYE1d55Gvni1emgy+spyQq3SFmpOiIlJetUnN15KozcFQd7NXOWimSEVN2KRo12xQwBKHgPHBF19nr96Pf/D24uL6/opleOaa46LXP52sDvXAb/T2yWUK+UUhHS5Q6MqIcdSUqSoVL6kYrXTdAv3F0XiPy9Pjjy5uL6koJKENltVX6HywiJTtXa9uPnr87vnt9aPJ3sHeHihDNr+7vOaxkkd6jP+wP7x356jP3tu8xLTOewqPji83WXqkODNdODLvOXd5wtE12pZgrIeaAXjSJ4h5M4rVkix5Vy/NGlwqUc6xP8BikUqrZzWElH9IokmhhTlK1x25KBXHL6ZCAohGkdqQBEnnf5NW0oMrfBMd9S4+pCWRokR7e04tqexUbsw4SuB1pKZ7lfVu3qLse1orVc0iRCitEkLyrGcgtaHuZgHKwbZOfoXP2d0oIA1EChIZU2HrBx7azo6XTz6aP35/bzwd7gXVDg2VWo3tTy6XdAjhiHMC4qnxdLE6u5k9OBivhozODPZ6yztMxMcPD2IXagbuR2ZgEiV07LohEeWNO3qkVJ08bZplVFMWDowT+kiTeBR4ZWfn3/zn/tmrwWQ+5Osv1giX2INChmY3j+aLfTZKxTRmrURZWIbtGoudKIAyCEnhLk4O6QIpoSvWf770+mj0wfd+PDufPrQVX171+893evfeeO3uF7/C56ll7ugR5MU8nNcMhRevxtEYqXX2voSv0R3lBsQC/+FoZ+/i4vrJ6SfZRZSS8yF8xNp5csviNabyZ/1nn3z83tkzyuaVvf3J3iEf2GH+78nZs+vlDMMe9HovO2lx174oBaUpLDlGdNzugq/c0RG9umZ6g6cLxmacuLVYmprWLdVLDRSaypT0OlU2ukwJCyT1HCIA8YxqxeLh1sruCH4sjJhgKw+6EBpWYmLt7AWTBrcrviSJ2wjFp740WhRSeho5MHUU0pJHGI3mJ2/phBAHrk9V0DP5lrk6yaGcEJBupg5eHNfgopryrqiyi1+pXzUiCkZC3cuEOuYaeKTBUoGelI/tGL78+Hj21vdXz94b7fEGLmMOdgyhQRqtk460WOYhAwhPfd5FTlbLd1mEQd/MjybZ+jFUZ2PY71/wroCNoRJgxE/X0gMdfoAtegO84kO/DuSYJ+X5k4RdwqzfHsA07WQ2+xX21GRWn9uxOpMUChafYDm48/MIlZkNMxlXuKU0nC3egYRQkhxFy+4w9++98au/8P733nr2/OzOanC2N3rwtdcOX/tSPhsc/vApZWJQiWPUYpNoFwSOnqbifqDZQwRCQ8g9czxin6bR09Nn58xXxDsx1IjJPebcecX++pYNeW7Ppu8+fpvPYb8ymjw4eMhNjyn+J88++uDyjL49vI56vZf2DiYHBzaDybdmYlyHN1XmvuzLF5vYa3F1edG/vOrtsMe5WTDPWhSdwLYe5Vo1xDqJF8cwhmP2FJFkoSUQGmN6QOEAa8Na5QPFU2ZBs+2XF7wrxU5i6O0EhIWoYVZSRAgOxQyq4UqNHmErHDQRMKl1QjR61sWzN7p+8l3WNScPVK6qaRaY48kcyUa3aVKzgw9Q2sb2rygwTOWXUYrQaBz/PRBYYVAg81yWJaAM2AhMMHTcB8OFWyRLRmfDj5723v2z8fT5gO+ds+yC/qXYVSrWcGbX2SastGFwnceOs97yMSM2Jxd03awxbJ7vAyGN4fCcHfIrt+qOGdSHuzpPKjghcqlqwJ2r6PXue982bDbUlv0yqYSJVeYN8lAEhZkTy6NqQ8tsICnZRqLEhtmoglJUlWQWc3S2S4S8AkdHC294dPDFX/2FD7//k+Pp5at/7ku7Lz0y1UYM6ZChUjEp3TSvR1cqiXTiofIBMyZE9GDEwyCdUSdaJ3t86+rk/KmbQsY+sBgzi8H+9rfL/s2U3H/w8U+fXJ/v9vtvHDBBeMhKtPOLpx+cPn7q22D9nd6KIVpWsfXpJFdfFEOSFWd4mW7MKhtq5Oymf37GHkKDMfcYdmQrBemwkiFuauSH7Jk3L8bIqSVlvQ8gd1CzHBCIGtGwCBD7PGdA2tCloGVpQpLKFQ3jhPzVmQuy9B5hG/8MWgclhSPPs4WGmqCiv3/ccXIRp3VYE5CajWevPvw7WJzcMKAYpxBuftPEJUiadV4TmC9w03x1jmC+zS4Xl44EBXxaoGaIpIERJjSZMIK5ZoavLqiUdPJwP1jRj2O0gx1ghrP5kMX3j092zn7aH98OeAJif6HcAaBJkZhFdp3YZdlzf5D5LNq9AX3OU/xwySdCV6/1Boz3TXqLQ7ujyyvDqsAdGh7enezB2hdNe8gjMkBVQj8GN6BllSQIzI9NeAD0w5dmNUfUNQZF5YKwtSR5D4pGQMdKrfMaUh1qUgORQzCLVZVDg4Sh4RKNCMK94d7oi7/ydTp0zGEozLoSlcpbS36dO7pkF1Bp0mFoj9AA5kdvhurDm3544WD8/Pz0gm/tSAkOB80jK7yZteR1pcXJs+P3nn1Iq/+VycHL+w8hvr0+f3b+8cd8dDHIdEAeDMcHB3fsi9qZj3shjpaQSV5sx3wStYJtnU9PBs+fjHbny9E1ybgY6+RYB2DzEbPqdOiHtfQElYkZrN/W/5SBhAYqJ4RxA9cogmIXyACIzA5D7U7OzlYBau6TDOowUDXHg7aU0ULoilK2pUnv7g5yx4KqLK8mJI2e4YqrWBJzRXUjaZVHs+PvQg4xWqN8mVnrEU/VMT9JE8hRdaXCiXsqchLtqpU9oOJnXqVGniVtcph5KldMapJtAIMEDuObSz6g1x9NR+PTxZgHjzE9Sz2XcsSYOLRcc5AZNihyT0vaJDqf/RUj+DRrNGUf9djdefRyHgupRvdXvce81JM7Og8q3Cc4tG1cjtaSRvLKZSBmElaMWfFKHGipMmrOa6/NGthQLHMmpKKqX4oJyi84SrDgGmYLxNiiFx9KPWE5JEnuHJZdO2LP8GmCmFXz3VlJgtSukJTxS3qYQFuP4vIq/ilZn/KiWxMDyZCqhm+PhxO29ji5vmDsBf7YPFnlYzt+VokJ+tvzm7c/eftifvvSaPKl/SO+y8orThfXJ0+uL099q8Ju/2Gv92Bnd4e+KKvYHBpNe0uNtglcsHMWSvCCE8MEKz5w8OTtm+HHt+OrRX9Kj9j74YB1FXGxquHqwJ/5jPnNlZDuoKQMqix3kJpRNYPiwA2glsHHyCa5tk3Ag2Vg0yTfMOCqDxmXY4IAmmMWoEMVDaEkq6ioddegCsvDw0uYm1g4EVkD86Ml24xVn0ytGo10Fp/uJEhtC2CKofCuJCGVJzlYM73jaFpTPGQVJrlCJSDlLhr6B0GWleREAE1PfzQbTK7HQ9smEqIHTWecMHKA4HdKY1yGRcC8dUhlYbnxQX94Z9U/S5fyQ96gYSMlv+SypEJc9FbP4odwwA8lNouulWEIgds2LocTUv4oxVgOD5l5gATHdXDU+ahb+Y9dkqnmkBbgGli1DTmFXHiwSUDbcSAZORZNxzZ1yHKv1jVwTsEVB/xQdJCW0EWTGyNl1uAbLcISlKjyURUDwI5z8Is5s4K0Gku/snpxfX0+56UxteYmibqT3nBnsKsK0+nj48cfTs92hsMvTu7cmexR5XnH6eTq8gmr1ZIrGuiHvRGTE6N9punrg1bIjZKZ4mdPbsXjhgMmRWbLZz+5XnxwOXnOejYaRx0E7NRtyazHNkSxQHS1Fmk7i4c/j1K2skaERA7KIOWSxBCEr09gVC4A0pYLeS0eqpmgtwEO3JFA5qRSpkUjSoVkUYJSXztni++u91WtzIBGk0r+aFvojlIrqXRVGUoX1E5Bq/z6MKMtYrWpo4OIHxLO5KshpF0Tt/C3eUac1ADLTuFPdzYPhDohgTHft+PBg76gLX3Y8HhKj5SVPqHiRN3hiYxXT31pW+eh9FZH/dXdXu+Y9ZNO09v6vW4vlPGb5cuOfDpdwdMjPUvGbFAzXrdKS+jTYzqfaOf8Pg0jrot/EmV3XN7zscairV1nXB42aJbRGoDmNIXth0i9GeehMrWXIiQqcXeIDDr4IMcLOFsYgx5fJmMzMseg4Mw7FPPh7bR3e5nnU3Tp3FuyMiVlyuAlTQ9bCe4uxzuz0ZgNXij5ietlWQI0jXK5g6gGdGADy1kmRtodkUKY3uww87Pqnd6c0wzSKYiK9uEPemOezRkUPT158ubz95nUeWO899LeHe5RTO9f3k6fzK+Oe8zt2vvjrd6Hg/Hh0ZHzlnQizL3Vz6kfXuCfZ9s22iXeWvEhpMfTx+r8o/nkHUqdPmGpl4YFPSEs70jFMaZFjfifMwGABWjA3OQ7ICRWf10l5HEgmXrgHqGNKDC4YgFB1fJJGP7BD3L4BJ80wehTOokc7rCFS2ze+BcT/RmCAVtYj55QQ+0HWgJiWhiWa3eFnGBxSdAkEBs6BGKqjMHUu9YGkpA0zqko0nAEUzkVt/hTp0zlhyPhY3x4174nmy6xZJ+dj5zCRQdvgbgaOChMhSpxOOH0+nZX/3CqnerCw+FRb3CXNycyY3GGv42Y3aDPY38fcZfsY7RiQJOxHu+2TPZPlrO77LzNilNdjtcIB+yRwgTfvD84x7eXi3MmLY4OWRa5t7NzMtqf9unzMoTAnZGHRhRZ3plP+Vrp8xn1D0cdLIeaiz60pWkuefLBnxTsDRUHgby/GK7mr8xv91kPROZR7uDoeP/wT8+v/vSnH711enK9Wu2NJq8d7f/iSy/9+r03Xp9fT66eWRdSqDEYlsCk7kt/c//RD+aDPzg+efPiyTMX2fbu7ux+6fDOz92//6v7r7x2ddy/OstIMJaDdMCL7s+HB4sFm05gVr/Ywlw5b76zdu345vryzt2L2eyj1ex6ORjzkSaGSwfDSX8y3b377OHDm8Hyx9PTD1azrw0nr+0c8rbufHd8Np2+Pb/58WD4ZLzLRCsLK/YWy/u8v3TnkA2OOyfEofEvBnpmfloA05MXntrcjLk/WYwm1871Yy7vjTirdZVq3h2t+mgD891VIsGVVOeKEtYnkhI4YSKpS4GKIGdl4DKRo3XXcNLSKQmCyA2n4HpZ56gkyUmUFLhMwicNoZFKjcTwhxVVD/1Gjz/Ud3KH1BesGaJTNcsnK1y5VX0OMq6VchMVYMR+jXCf+mxlxdAzc4UhGGUvQcC9eBY5PBJJZ1Nf2RssXULIV10oiJuUAkTyc3SUVSq0EXFCGWO6m8VysmKEUyDtGM9yB73BHSYMM33PUpL/47D/E7by9TZh35aNZnnagNDHBjeY6f3mcvhfvKWpXNE2En1r0P+7Iz4zUzthcU/WYq/Ozr58MNwfjP8X3/re77FlrW7FI417CLNo7r/w8v3/0l/5a//Dv/133zw7tZWlkmN/nY/PXtDL1V3V2RsjOmMFNpta7o6H/5u//Fd+DSMwI3D46N8/O/u3/9E/+MOPH5/wKlHLse3w4XD4Ky8//Fd+7S//i4++/OD5e+4LgOfLhtOit7P3/f17/9Z3f/R3fvL2B1eXOEAaLq1Bw353d+dXvvSlf+VXf+NfvH905/lHVRuYaLgYTP4H3/nWj08v93EC5gPJIgXHEgdaqRu+2bvkraWT1fzn9vf/c7e7bC/yt8e9dx2+uxye/KR/ujq/uv6Xd4/++eH4zmByORr824uz37k8vpovrwYLbps0ZF/tD/5Vtvk+PBgd7DHBmk4B5sCQZJZXWnhJio21EMpm4FQOKhw3rsFgNlpc+bk6PFXHqDXd1pRUl61zlxGTAm71vsLat37WGhHqLLIuF25YMbQNgmoAKmlNXjhFEiBIwIpP4UuSX0ib3IYQ/hLgBBBGifKAosJxRsfP9BbKM/eeEGoQhKSAVdVDBytSIrCj0uiuZoxfmiRMS38ZJ7SiNRcOqidZyNRp+rg64RxSGYCf5LaBfCjoYLg62us7OkkSXqWk5AbBVG8KNE5IJeMggzSAFC4jmZlCrgEV5gx5oqOXtvpwOPrTxfyHbPkV+8jTpjWmzInW8cFwh7Zjf+XgKr2v6+XqzdniBIdRrHcKAh9dw6HHKO2fPH3+986PVTnKJ3+rv7A7vj569K3Tm++eniKhjKN+ud8kk/BQOIc8o8DDyYR35+1vH738b//0nf/pH//RM5ojmiVuNuI0Pc9Wvf/wo0/+9OP/91v/1F/9737j6/c+eSsFRKGxke7494d3/xv//n/wbT58SzcibwBHjFWYpQtPp7d/+80f/f4777z3T/8L/+2XX999/r7mHgzYivUPHn/8pxd0C0CPgs0mGV+LkkAP9g7mfHF+0f/BYvEtXnAiE5ci8yD39YPxf/3w5dHl5bur5d+/vPo7588Rp6lIHwz3h+P7O7tHR/eHDI3qZtQoyopeA/ZnzfeMrZSZonCkhszaveduNezPxtdXLERloI0+q8XDz1pielPTIP8FqQqU2lEQUyoJYAVCKpFpYWgr1KLBSfUSkmodbmSj8O25ACfRQwTPVSE7h4yKpafExdNAU6b8D8cgN+rQKeMz5mhKn0yQhR0SkytfSKlIAolY75IIthSpJlUHTVCg5/xD4DWHCgffHDS+nbRKwDJUDFqzId8p6Y92zS7LXRDmXr1144IV5HFC4LlxyJRmCt+gQaMbiVuSSx4mcCRW1zBXQfhiNGYHPuSCjJpRTNXgUCfaDWr5hbPU3OlZK4Mz04xxR8E2kW0pkONhn7Gf8c4qX4EmVZvFCDgRI0PLMWNJKtYyq7gIQVrsx6XMUtZCWYb7d3mxYHzw7z1+8j/+oz98jgKsFAIJ/7c6kxvzyD+zI+er1f/2H/6DLx/e+1fvPhqePsmNaX5+8Pr/6Hf/0Z88fzbKhzhbMXcSaUlUnGWlN7P/1W//+3/uX/gX/yWe364vEE1fGk+iNeffXKitXfHUl4DSwvNMQI9xwTANytN/0GaQQL/6k5vL9/uLn5+Mp3xLxpYuIwIgZC99ZiQeHd0b37vX22U0h/QIwQBw4W425Z0nJqAWbkaKvSOQ3sP45ujq6fh4yhtndFj0Wn7aQ+24pAIRN1j11EqYBKZWTDCKkKqWyZTAGKvgYomQiljhYoAIhQEvASl7EWQcRAIcOUNCltRH4jwCp9w0jcjhgoYWIT/tJT5KBp+CESdOuHtkMuDSvK7BAyajzUGlqNwiwlomRaOULAyUnqO0NojK5qGO7lrEFStuQlCQkmJGd2/YOxoNdihrGq9MvzR6LvZZka6pzVOm18GixUtLKJ/0SHksHB7yOMcyRj4EzQIpWVAWpCcDRpvSgBifOu8NGAtlQM8RGtFCETTpcA8NwDsTBKxQ4VYneTKgT2c2nqlxrfnkr7KejGqz8ClafYO3IxhvHI5PZrv/xrd+7xn9Mdsx1QOZ2+bBaHLFu5RIs8zM8tly9W/+wT/8m3/rX/gKqzcZCR5P/t0Pjn/nw3dxXetJ5MK/Zr+o4ihrLukX9/vPZrf/uz/5/X/2t37jXo/nXME0QtYi9eIEKFdzVoqqIaMoLNeeMIBAOVpGklTgo/n8D25uvjGZDFhJ5ARgblvxCcJ3Jrv37j8a7O+vXDFRmYYuSrIUYMqmbfS42VpqwgIcy0WLjCbL3evT3Seng2ueGR1pSzGkCkGsZmEQPvBSdeEaNwYSpwOmAKoO05iR3HFIhs0v3BsQLpY3yps5zqYqKhAJW2dVfORGi9yvSFIFnKxYGQsrEwjAKN4ZjwXFLmlEN4P2Rq+8Up3u5MHuQrN+u5ZCymhVinIzxq98V0EeZiucoyMhlRcmaQotPIRrby+ywe6YL6RUCJjQbdnl6WvYf3mvv8OtmnVR3H0b70jimZCaa6Pn9Dr+xkFzznAcY3EwYQqWN3p4R571ovRIWap23mdBdgQmE+ArtoRLbWYuesuz/uChrzzwWMjUPwRRWwSOyoNDgDiK9S3A0Cbb3jt4mHWUj0bYg3scwNQ+c5rDnPvtzSoasdjne9S//d2PP/7Ds1N6kiRbmJyXi7/w6mv/zb/46/+Hb33/7737U5ueWA6c7z179tsffvCvPrzfm57eTnb/Tz/6M56Bq2duKa16O8PBf+ef+rXDycG/8Q/+4On02hJrhu7/0ePHf/js/G/uc6uhLrgIUCUsOfTzDIBWHePQ+iGS2sPWO3yqivfi8cYUGu5sB4GCY9zod6/O/1P9+7PhLZOL1jx5aVv4HYzHB0d3+3s7btLcTKYlrDg8EF7xKUQGzuiAOuqcwod6xS2Jz45cXPYvbxyVY7beNawhQq1UmlzVlmhqRsLoxBVM1CakDsl3TkYTl0Y91TJomTMMdowQniaYiyat42VaQJ7sokaUsLJfSIgaU0qaROwZQWHiGIFrE0SgA2eryN/o5ZfUGZak4IHhEPGEgMuQKwylhENziHiOibCDSBOaatFJEFIhQfDaeHmPrvyZEm7i69og4UKMk7HO841l7x7dUWYIUVVS/3Pyzj5gwQaoS+eCGTSHkPslbsaZcUnmA+HGPZYfHkWtusQ5yGseVwGoa9iRxQQ58+LF6rkzGR6stKLzpAHQLOJLtrcwvh9j38mKHR6coDa86A93e/O/9LUvv37/0GfTweqTk+kPP/o42zqkjtEzWa12hqNfe+WLd7Ir7nAy/ML+Lkvr/v4nT3h1g5zJjiUTYfjPffnL/+XXDt999urvvPuuujSR5uvvvfv+f+X1lyeL3R/f9v7o2TN9KcUJObSP7hz91776pdfv7n77o+f/5+/9Gb1oyFWR+9Fi9sfPjv/m0Su9+ZTB565JTxr8tcTqpZ2Dh5MDM099GYy/PJmMr879QFXUgwuiHIHjHc7+4o9uL9/eu8fmBmz8BBcPbwSGJ6PJ5M5+f4J27RZhKik64fz2mmXBlBhzTnwnFXuTPyQMJjuTew9f2pte3ly55ykPvZiDSX2cJZuq26iDaH49lyzksQoG5Y0iQfGELZrI1HhmiLT0CaNfoYFDovih9CJR6OBh2VYtCE8T6l5TzK0SUiiaUKJNKAVBRpGbRjgcgyjH0ic5AGN0/54yUU8syZEYtk01LnogQArI9HAomWrOfzCtzjDBN0RtSCYabThGkiqgAyvafpE6cKYlPFz1XrrtTXiIYwCAvhgjlJLxiwgqKU5IvfIFJt++xQ9574GqyYxWHgttFq57A3qYd9F6OD5PblOr1nySm9IjuuDDz3yYHEzZOlpBkReZppsHjf3iTQYAGFheRsxTPzr7+H/5i99gbxdiw8nq//bx7b/27/7t4oQiIFEwe8PB//q3fuNXGfZfzXjtjvfMF4Pl9y94SIOLhoNpHXf5LMTN/IGGB6SIQiDww+cnJ8vly5Pxt5+enCwYUcLu6hxNlgc7w7vzy/2zy58/oK/AATnFDnPvaW9dsoSWqcfiKlsRrLL6Pveef/3X//J//s4r5++8e376mKmK28uT+c30yt3QuuonOxp9Hrd7P53f/tni9ud27kBYvWDvjjlYysN6uj7dGe9ZEWTekON2GAzA4mE+hI9HLMmNE5K42p1Mvvn61y4Odj+eXc6YwOdDMm6hf7XgRX/32mAljy944IFumKFfwg/NqT780sqRm7RTKR7tCU7C1GWxyEcu8og/tGQUTcjqIpk2E4OfGfZixLDZSFCgco36D4FwsuiZqKx8UhCRmhLyUBPOEDBvzB4wIOhDlzZqgsCLNqLGeJLGyWLb1IWE1nVC4irN1IIkmoNwJWY4QE66tCptKAzSMJMznIcPVt+d9/eYr2IGhaFu75Vpw6zqYcIzITsl0Idjs7R0wyhIChk/ZIyUdaG4Jw0gGl86NrO6HfRPWFNsZhQKEwKvTvbYg/R4cS0EMF9qWi3PrEeO6NhJqvuXeRAlSHJg2py9VapNBWq6RGLMR4ud6+cHT4+zZRTTBre7s7vkHCoc0KIRzZ7c0c30/s2Fc2XUYxYV3L97ymihrEqYcoAz89mnlSygXICZxC3ryXR6PL19eXf41sk5z8O0JSQkWQdgPcuIgRJeBMMqa8uXfL6Lxh47MWVct5husND3i7s7j66u7pw9u/f88ent1dmg/9xJRL8fqJII8DDMcbZY/M708o2De0wnCe74gcBiNL2LifikJFOE6Ii5HcZ8xtQTKTwS4qiMhbWRGzo5r9556atHrES89iPoi5vF/Jy9Fpezy/n8nK283YiRj8IyKcRALaue9cc8ApetERCvo2eCPck0fsfP3GMjerZRXaTkRBzhPoi1sHUUiAgmNccubJMkBLva4+DgQcClItrcD5gMRM45/L0LpKhkCjJj2wT4Il38QlmaRBlSRWSFAVcOyguaQWPYKhHgYAaujaviCmzFHjYyLCfalGOcBxIwSYIZTsibMwfL3g52vWVLBe8VMsYDyZPqg+fOOOzDQDMoYfU50yPlywkH9kipfDiSbSNLXk5HOydsVRQBqheTfHPv8Phm9mzB81Ieg12oj68ijJJziQ4GUO62HZTOlKJ3KpNM9wrjZixaSaYVgOmjQH0LCCWbSSXxSDotbrB53GG33NvFFL+IYkFpTJk6BzsOHz1yMv+D/pQ9rXkXtj95fk0uQ1ontGF0lxcs/foLw1uUrq4iaSyHrhe3vq/LendQlelhEFHa184r3/64ditfxsVqq0WeYymDlvFQhMQK2l/93u3Fb/FCEt/QqZTqDqX6KZp+q1pVIvZwfROLv313J93+4cTlQRlbzc1zudgdjV7evXNFL5hdKenNL9hoPft5sbSBVQMsDaINRRzWy36o8JS7vmZhIEGvs9nhSGuTphA4UU1htkmqiyVEacdXJTdAVeOZxizI02vwizyEnICnxQMfDNzbXCasFANVefVYkAVaiWUJN9xeSVZw7qEpHhMVZclw4RxNc7GeCeBM0voIsBALv3BETOdIRDOVa2jhg5WUuAaCQuGLKHOccG/eZ73WkLFOVtLTZRJObsJIXmTIBRY0EpZ1HlPyiKZR6FLWW3081DFSSfVkoddTVopawzc6En5ltGPHkiYPOLxj6Od2cGohAPdqGIamZVkFNDVa2uy0Q1gMxpkq0XJLNswnlRrBhSBVSyVoKYMUC9IwuDl4xxECwjERtYxgSmidqp3gw1MStZ78sxfTWoGiAgH93FqgfztJRdfTi6VScGD2+l/hhJYTmki/yRTQPV5JWcz4lhlhHhppUWZWC59zI8u8IUHteeLuDz5c3PzJ9ILHgpQNhdkOPzre+qEI5pckeLAhDV+zWNghZiOgEXsE026rD1LUlXvA4XJwQLd+yP4/173hWW/B7MwJm3v3hre+6ORwDTVY9RGC+grgv4UDocIDDKQ6hyJ7WKHUx7Ph/OkfhrEt4NAKCZ5EFppXA4VnKYhMjH/WzeZaTIJSbMJTDKmkFVCa5J0MmIy4yyCqqhV2rUNrqKJF15ISE5aa084FBApwTRugpzApOEwqnbOBMDFcgdyECbNWY8LkM3NtTCRPl45Po13uS9En9TjiGCBlvI0OKQXLcw83/ExL9BneoDEkihNSzamnZ0MeDvVCRIXUwIMhK2Xw49KGizk7ZUPEYe8e6+TSsxWEgsgsoweJGysik2RyDhsaILyLk6qvPcMXf+ZxtQmObRWIeJ9j6r4CgAwM/cRi0TRSYzzAGashC0IxFfwE8ioOr2lhGsegNgcZNFVjW8MXf/XVB/+T3/hVPnLDoMac+daUwtd2+OaLLZQsVfzFg5f0Z7cfzeZXO+OL1Z1nN9Pp/IYhau50kHO7J2CxFZ0WVZnfn568NrmTFo8sATSnKOPNLQGvlUKmmKa/vqajiQYjHjVZVurnSpMcc2OgvWXviM/zDJ4vBp/0F097g7Olzw70IJiQTQHDXQ/wp7fkp0jrd8BJrfxhKpMapj7gIaTUC58OIHlLsnk0TFIunGCf/4CsDeEGjm0guQQxNUYq6gUMtFUJjHLlhyXChhQOIza54ooFVIez1osJ00BVGMu0IxWhopwFV1IH3yQBT1KDNEYWH4c1J0UpE+58qi0699IhoxXX/RUbNvixTIfkLD9bejMYpo4eMAa6Mx7wmUHz0L0UT/+MxpB1Z9xUnevLkOkl+5IWd7MIH6cfXnZXUu69yXVEo9FZf3XZH7zu84+Poh6aMkfUJoSfAVV/j0oMPbdmb4zFi/yIpoCgCS3LJg88zXjjh7yZg+epZFC0UKTOUlz0SHOrKD51NjNwpzKngKNZ0xIZqQewdvuJvT//aPjnX38Z5uzbhE8GfzlihvySd2ciUZPCrxQFxNasi//e7/32Pp2R0YDJPF6W/5cne/f5eID49bNLKXGEcsL3fjS9eHLD82c6tBtlqUOstOfrn8kpZ7Cdpqf/fcW23vjzZP9O//AoLSFkIFirMSV7C91dzW9Gp7f9k96Q7RCmrFTMOkCxPDjnV1w5S11neBBLdBvefDUSNsi6ii5WPIvKVEUIL1ZcIBdeEItX/2n4+JJJxDqqDT4wi9M0DZxr+BQBnEbHzzFtCiEFIRdwdQ5N7k2fCCEDlgJYVh/DHF4NSJJrlZWPLcEXNXShlU/Yhjrw9EEoOh6mqG0jZkrwwIt+/8pxUeqarcGmLxrByIT5mDdj+sOpH5RHSfxta4zU7WF4n5C7K+OGx3wLgb4Cqzwr+8xD9gf3bDB5dGQsVDXMBmgr5ut7fCCT74eitkDh5rAu5oVBmdbT38BjNBJT+GsSTKHxZaB9LAXS7KPYS/VhzZg8uQklvCVKLq7icxotDILbnUSEnltWeqwRYO7CM3XhlDcnmWi7Yraftd3sJOgyMF+4HCz2eovXeE+DnWG8dZcSkq4VesKm9xQVw1wYYTi+pd30za4XMBWGYskSJ+Ynnrhannwkz3LDIyHKYx4R1cuDvU44nfE5g95yh5HrO3yyghFinkP1bc3BmW0s56u9RX+PdykGvEhlb526AMfc3DQpWE1SKKRTyobHOirXkp9UovVrJFUtUnQWYNyp8ZEuDL3EzYL2AoeO2zawIUtrsYuiDrkXVY+0o5Ix1Z6vd1AH4mzENTT4VXswIT4ChOTyH1M0hfZMCtEqRsDWuSTrpcQDEk1jpZrp64FLnQAAa6DLgns8iuwhy437ejihBU97hAfmrtOqsdrATr14P8FxkNQDnJDnQAZjqjFkbIbC36cl7I+e8B1CxXKIzTHh03m95d0la894Wamm8dSBNcfH5rnPsBD6qCSi2kHM3JF7m6Hit50GiIFPhvhsJtFJakI5mvCUiaRVlET90RsN34bLJehc05Ly1E4uzfP6IIy1/SijL2V0KYUUhij6t995+j/7R9+pBlcjx5B0SnH/bxzd/b/8jb/wBbIraYQRgmMi2CglYgY0jXWDgo2VIwuliyYE2xaCBB7FRmXtjFXXQHISU5HZG+rqasZm23RV2AzvcM9NZXBClYGmcwJ22bsd8iIbO14yKGO3DsHejrkqxLMF4SGobFnwihZ8nboOJLWataLdJreP/yIro+I1eEVtsDugoeAUn9Iqdkln3HuzB6nWVZE0eoga4Yh3aEjJTavSc5bKGmdRUK0454cNhMSkQkznh8MLNpKaqnALb02VQFGlYoIZT6aaypxiphnEpXiqYhUG9z6aMJ/n8kyYfkBprSh5kS0kstCQ6YrcyWj3cGDGQnkyZH6RMA+Ku2APd55x/9dGGgAyAnwUj01U2HKGJpHHpayBU2EGgz5e8UF71wK41RAyyZEBpCI6ma+4iRxWx/AVibevfFnCQ9NYmRFMekUFGfHQCFU0MsAAsV+Sgh00Sk+jbs2ni1AGAIEKjiF8QvMoiS2Zt/T4cuMxZ60siT+zbhWe3DAOkHUByUuoJW+eRhCNoCvGvOnkwy5FY7+ENBFlmruvsdBGCulruqAhjRGuoKhBBq/YTP3y/HbmOp6dvZ3BHhvP5Ple3h01tnF7IRZ3MzjAt0HsvzsmZll7/yUbVR5ShHFdiClFDQPlVNc1WiAmdpBGUk4VFXSwLSrYkdn0Nu3EJAlNIjB8qhmq7mjHWRWh61xd0xrnZJlGTDtZGUa8ssLhukCsyR2nJuy0G1Q561qECPOzIPJntYSPYU+JxvHkEgKAATeUIMNIuvqJkArJXCaLtllgyKppXs6zOaMD4kRBNI36Rqx1nf1Qhs9ZshzK7WnTkPoKKXd61q+5hA0nZHJ5Phg8d+AqGpohOd7heWfpq08salNE/jnh8s98jBzczX1Bu+SI/JxAwrG0hXyKHSjEiCimRgA6Mnyrgp5Fb7apG5EQFbDXac3qDsGFnYJ0VrwBZGGYZKsF1sPCAVrRCVqKpi5W9/f3eC8QELKoMFgumK4lta1hFwLbbW4UkRaeqidDYDxmY1IempmO9Qk5dzpxzXqUQ5Hk2hKERQTLKsVb7FSEjPEoZ/tGCgGaQRbKXF7QO6EvOrlz2CsnjNpwKWYGWCxHZDp8frO4nC0YzvW9JrOPKBhKEAUiP4AqBaWqSGxMKA2REN0nR4QYJlFCA17E5Y/ca0dhDaL5iNpI8ggAsMjit8U0PomBQ6V/dty0evRVSJUQDgZGMTcLaDpyDa1lYbZSGRIqmliUE3p66DMpBILCwMQqORXGGtEk2FtXRbH8E1Vo7KekYkmU5qO/Q0NE68NQIDWDjhLNGU97tisxEtTJWCqUEX9MBU+GIzaZNUc+BDIuyoPMVUZKaQxxpZP+8jnv5ameikjX6x2xPf6CtRq0hGppNpKAVZ+xGfSgfz+PbKJ6SFMYnEXnRaMup6YqXNu5xsqwKMUv+CSJoJDNQe6tYwGQQFYtmFDGshW2tBj+zM1TCfKuA6uFGBPFkCZEz6hDt/OVw51Xd/cYLLn0E+HWCwnVM7oBcqKyGBbXYg/C8r/6jZ//zTuPzp49u+ETEdeXD29YRLSYj9zrZa2EhUrvgx1IcHmgcfHwg1sMEos3n0USlZdazNPmxcXNBSuhFgc7+6N7d+OElH8UKyVRCnzuBfPJgCUDx4PnLG/zcRbVk83KrDIhCEy4YolEFa9YPLWwMlzVLSxEa6Tqil5ekNhsY9j8iFfDV4RthwuheMuco8QpNani6JtG1VKl5OzjbqAiOC5tFCz6cdwKVyPuTcTsjpKaugK2DJpFC7x+vCnTyrxwlCNni7OuhrVp4RACNRFBSQx65MFEzlxY+4zH7Qzdz4ke6ZK9nnlfhjTpFbI5ZGOOSKIi6bSVb/ufNIYuNyRPOAS3mJP+7jkbrBW2LOTFrf0DcuqwnU27IkrMqn/Mm6ypOPVMCDpCfCJJPiBmhM79cFkq5yElJ5nCmvaCoHkCXkyTYjQ6yzlGQiitv2bJ3Yj0YiEVenEOCKDtvc+EIBSbMI40GzFeQWjVSxEyFBf2X9rZ+d//1d/80enNf/+P/uC4vZrsPZ5Uq4CKgOkzdWSHUMk2sL/+ypf/5f7LZ59cPDt7fDw7P12xN4YvV+cIC2g04OoX795l4c7bNxe5P4dTyrrhJr+VSz2QF5GZHjy7uLm5YmHTLp/KODzo7WRygttNdSJqeKZ04zY72705HV6xqxv9FMrKzEVjVSU7WhPV+dfqnFMAgC0HYkBjEIOdzuVzmKhLEpOS66LEkocQQEffTxvZEw2cyiZzzgrPYbpAPRBgcixACGzlLh+jDspVNoIvNk7IFiQ2g9HVtTuVT28jqYwwtBpGQ5OCaYBUs0hyKmE7i9CYhcaTHLpDCmNlJ7naGeMymvQOB6tDWjZ8J0NlUTmUKJ1syAdqc9P48SjJTAMfzYYfmcJzGIxxnj/DnjjKc94kJLnsaDYt5G/dXr/Jli39wZX7WcAt7MhBv3fpDkUaQR+JkJi0BcGzSGi5W81veqiXmeAPFUjDtezq5d2DwgmHFIwC7dnE5CTaHXWIpbLUJCXi+8s6CjCQU20KKd7DkiFuz5OsjS4FYhbLFhPeGQ7/+t3xvqtsUQMGxTj05JdBVQznyGvJTWosgEZXveGUz8HwLSw/Tch+a25BQEHtuPil0wApq+WfOzocHfZ/+u65gzkcucV3GSObPCqkzvpkSOM355Mv9EX5AvA+EyA4Ibs/OQPDKlSeQGIoNyYtbTVmfza4Zcm5Y6X6IQNFVmB+WK8ZM56gegBIUI5GNWSVCJAqVn9p2Tq0EAXVOihRUW3guTFk/SLJCArzoMovYjqhpnLY1deLrLDyIR4ilalGknhVVxCsyXJ1w6RWvKDFfurFH2HwzJcxbCO+rpfMVWolCwRL2wSQehsqqZFaiWA0w8FGHFJhY2DIsubeg0nvgBX3fLyAVWd3GWBRXx/U7PHklkCdhBc/bstOIOqu7GyUx8LcaRwjtTGkzOll3w4GT5huRGEfZ1KR1NDVofRawcrtQ345LByQT5FqzW13hxi7EFSWCXiZJZ+NzrA89JmqpGbb7NFBDXPjSUJgjI3JW+0n3V5e1e5mVJFhkE4vHyeLcZUQ5qbwshIDvL6HsXDPpXDvtBCPwgKXynDtJ44xlAUqQg4Kwb6zt6N4F8Aq2bT5xCaMZy2mrO9k92UezXhIJzOoM8orLRgTXnLs9w6Gw7/x4MG/9/67ZzEvOpMFhSCOgHd91phJrTfC7Orq9uqCCPsBsXFidp1BUXaq5Mtq7NQDCSvtMGNUGjJuujNcjBZ8VtJ6hNUc3Q1vMFHaU5swSq7QSqgHdY2IytiGqYyU+QuKXgFINI4khl1aHEECQwWHqsOp15EjXLqCcKnOqg1EJcXoAtVcjwEbJmVw67XC0TxXtkZgTq0UgR6dVXvzb2ESj8+0SiDEzEX5qFoEIQ8nMLg2lKIX2yaySLmQjGqWJ7eBvVHv/i4/B1q844HIkn3Y3LItkss9fLk+xAJxS+8zgbDoaTLY4Uv0FLfZ9F5MpxQnPO8NTgfDT3QD788Slkb6pJU+1lQTjyQhmN1ZniASxWicK4e576h9/MaPXrhXTVX9Ii56zgyHpqoU2HJK8ZEd+UcH7YaTE4+RjDE76sqBjldTBUTeW+S8ZG3M/7+vN/nVdssOu96+Pf352tvWvVXlKpvESTmOjeMEJ5GQsIJgwCAjJMSACYMgwSgSE8QgfwKKxIDMGCQwQjIekEAaRcGxE9uJy0256jZfd/q3719+v7X2857v2sBznvO8u1l77bXXXmv3jX4CAB1wnP7UOu5SidTPXfvrI3cVAEPYkm9yfjYDVB5Rld6JAW8AFQ1PoIJjmgQI5BixuF9yO9/OblZeky0zaduzizJKa/yNJCNjvdlfPn3y3V7/N+Zs24QneBX9xKzo4hbr7F20zXrRh9HKM4FDvF3opWCam1yOTFlL05oBNazJjXrjdHj+0eknryf/lk32SAFnkkCMaCUB0uUHRnBo8KfUKOFiPOGLjViAlEF80zGCCEPtiotPcDe4TZAEDi8ygVCB3wQWtIblUfEwVpTgSV6oYDqGe8qBsZI40fCCMRIvjhaXVREqFMSGEd4hrjA5YvBXkRGBjDcPIh8SwJiBS4D0C1pxieAGlOwES3M4GAQY3JniO23Vngzqx70GC+6dmHVcNFBSWuPNk3nJRB4L21xZa5mjIzhY6NiuL2JUBHZAFt7MGZKrV7XGrWujQjikkn9TQfplgyTEPz+ZJnuStXesp/KwYDbXRwyRQAEzF2g/VdvwEgVfsVA7UU+6DjsoD/qNrMQhA8IipOM+2AoNe8Zpz6jQ4wmYUm9Hu3y/ZH03SE1WyQlQPO12To7atdnmg56dquhliFeBglB2iBiG2+aXDGnormAQswJmdgYmyIuSWpfMNRHU6l8tF7/d6lwNhm9rlw+cibbfkLRtvcMKFsWDP35ST/a7D9rtXz6/+K3ZRJWQ+oiO34gkFDM0HSWczTYjls0zHM/mmO2eu9DQccBRK2QT1rUpNyh8I3/Bzxxhp/P5i0//7c1Xi/k16bBAhkKlJsIZh9mTGaoUW8WYTr/FV+AqiB7hbDqKNqi3wJQOcwVhKCPxTwBtxiZeojskNeIvgJr1BnPQJXPDHKxJ9QMCGxhUUaGIaN+6OAupTKKhXx4bnZ/IHczFTcIyHmAIDmBYQWWgCBK/YigO5Uc/0RTQDI1yIG6Mi142a0+61IfsDqBFyGYzlu4WMs1synriwoA740hSHhGohKoi14G2G2w0y4TpSQ2CHnJU34iRbQJEvAEtmS/aLHO02zNhiYzaGpyqvK/23gZz1KyfbptvXSTt+Dz0Ix2z7ebddv/tRu86x5SrIPn7cnhExa0ZaBLLG4SGrxaeYAHoolzQF4HbdXa7752c/m+1L6XDQKRW4zXtt8HTP5r8iIxVNN97vv/kYugJqLtvnx1fNFs3oXPpT+h3y8Xrbfvl+eB3fviWie7AZ26bUupzWptW75GryAlPmoNcjpL5H37z//yfykmNppy66d87Ov1zHAoJixM62hEYWeTb3m/+2sWzv/f11yMzxkeV9pcP0VG5kQMNdvHWHsar6QhpczCUbGHUOkPwVY1IYjAJq7ZI7772/Ozsw4vn92/vnKNQWwt+QYow6pR5nKUUXrw4CQMxUURhFF7+ktwgMXkQ0GGHiWSHgOUbPEssAaVfUgduUIFPrUxRzYBKm6ENlYVF8Q1Gh1dqIKwM5Te21tmAABF3ZH8hALNIIVMgfzBEroVHMSkr+meCM2HGj3sgkMAEMHA+pl837GggTSJaj09YNo0ikSzqOsTYHGLKou5hh8CphLx1ekJ+OYdwbYFY2oRE1qr3WnU3x4g6QZ3polXFLjRzU9fME0iq/erFs7+w2TzUtv9q2/j7929LwYoIBK03td27WvO81njZbP6hV5oktaDgZPbd3/5Xv/P9r6//2dXXETv0iJB4j+v1n332pAhcqmLkcdBtysEigxwqq0s+5QRWcVOyrH7lxZP/8d+wYk8Qu/ZUfBzT+Ie/P271/5ev3mAllnygtr+v/crHH3eoRTjv7Pz0Z46O/tHoPiDAb1XCCuy//c9/+wcvnvy9f/NvHZex93tAEBlE/nOGBAqXuh0aabYAta9Pt9vRzpFjHgCgaUnpzkrdkN73UbnXqVH/i6fnH7e7v71h0b3hM6GEjXqApTDRVeBAmbv75XLZb7Lb2TFd5pdq7DaUAEtYupwOaxEapYUnZEbIab/b+fbzj19NX41W9/ANgQlnZfqQq2StceEUuRwKKVwIhPkumysF8NeCKGF1R/pDwB1v5TENAkVaIpREmTU4yxR9zaiANURYwSGctZwGviSDfnmMfZGljMm72h4UsIkv3sbgb6tHN1yqw0dHYjF8KJ6YUgDCOYL5wV8oo+c36IuY01kv4SVbzwDKxOmGQ3ljInDIVvp6o09fa8FQhlgjAExEDxvooaUkwyFM57tdgmEbxgXrO+YSORg2ZYjyGB3mzmaHHVU68PNdNnqTNVcakGDKPhiiEeX8uN362YfJbLud9wYsYQNTZJ+iT8CH/e4rDlvc778z6P7Gmr23uJUE01r6zdvr37y9QjSDn6AkPWT/7gdPX/zc02e1h9fC2n4xlON7Juj9UUXd2XznvkkBeO33/qXT47/07Omvv33XZCOsGSLBP7x9+3f+ya+pV495wHKA3S9cPPmVj142ltQku5PW+j/5/NN/9ps3bPyTczQbibvZ/LUvf/xrX/yIwWYYy2G+95zeG6QAQR+NIzoY4yAJDJ7iEsoTH2lSIyJ9GEEJLbs6l080WjHKJP08kT4oRW7rL3rtnz85++2b7BYGs0RJRARFA3k5nnkxfbhl59Lw9JQOKyO7NSYJGZAwo+gxuDRApEhH5j9Z4ouKND69fPHF6KOfjFzzTesaZ8QEEoK1QDm8IZmITMi0FnLGV8fHF36pKjJYoSI8igoerWIzal0zSIYLF2pgoMxyHuGTTKgN4VJ8eCQgQYC30raNKRaTQhymhreAOdYhJqwtzhbAyL/Bg8NJiVhL5uigT/jGJ6gFBRY9RVSeyqB0qu2BMhwTeWlfB2aFgHYjK6ppUkIyHTIHXQJnpovRO4LFHurdjPKz3uhtGx1nzjg6W8bwRFIIw5Z8hlJoXBIbHmQs67aZhNBi+yPJpzatH+9aJ+vNxWb9cucJ3HR6gq8iA2zV2L/Zbz7aNL7THLDr53dY8m9lIFZS5MRA6CwSE4lAAbeXjeZ/8YOf/3g5lvdCJTv5dYeHEgMBBUeQk/mQ8Ula8+l6+V/94Kd/9H/c/9GSvXtogdxzxUuqX2AA83a3+6DZ/m9+6eefcyaGjcNdbbH5m9/96H/90U/+4cMtAyoyA7JifSzay6rtX/3kO6tN8x+8+n23RAUNpHcKn9dtkkK/V4KIyDxEpgyMoJCIxwdF4B5PeQho/ENdWOvc3GSY7b9/cfk/37xy3Uf6RyZ4FoW9vhDJ+rpz0m0NzptnFyzbtiHQ464dEIKKT9Bt8BBk48GFkgJr/bjb/fbTj2a1t7PaDQlUtSsCybrgZWEumA7dD1MEjlA5FTIjCRdVIhIoaZp5KN3gNbYMImEhF0FdJAs2gCXwEanZrFJriHq1op1fUElk1ool6uSwyCMKQx5GpjgGIaMNyRGgYiMGjEGrcmLu6AKS8qRVp3AADb/pmypEjOFDwDRoo8gLKcUJJaSmoG5DfzgzyJWWFjhoVzBMYFqr+waFuBPwzLrtNxOGyndMMnFMXoOxVKIjPCJVZ8uPZ1sQUSgugTtj5icYok++4RDxcnTf6Zyzgxj/2Z/t16e1HWcYZbqTSjzuEEROahtP/+pwMN7u/5jyVwEkhCkir0JKodPlLN/qDf7rv/Irf+PFSevqlekHxORHJ3azoohjL751QYQlD9Axbi+ykQZcsovvdvMrx/3/7q/+4t/5x7/xwylFPpH4KOoyg6jZVb7/7Gj43/7iL/71jy+aDyMbUa5K4MaV1X//l3/ub/2jf/67kzGsUm6D82w8/7Db+89+9s//369e/YNXonCZDAXddsM9So11p9FbNxU7qivxkLQQLPOK5mDSVhTP1YzA2J5CmgI/8ZBrFFkm7pcvjj9vtX53w5Jfc1sU0Y2obRlkg9ItK2OGH39i+1Pdiz2SLNomdQzYlAeOyVxZ9/5De6fbenFyfrV+cr1m8QxSYgJNPZARVaRNNik4CH1QIMOBiW+6hCAYMjRHQ/xhl2kCZqMzQcznZEqkKC2pe0QbEh5iQEDAIMYH0KABFOBsUGgaV/BLgxEmfEQapROOnkUCCiUs0v6oYxXSA0/0B4eg4cZPOGjPJ7zEBDfDS/ryIfqSnxKKHoICKPoGdo6U5yCAHMHLN3GFKCMRgDGLjyRs6VAxcM7JI/s2l2FTkjOZQXUz4P5mNp1tud6AbPdh2q7Z+Har/RkyD+lMH0RXYNhocmkEJ7DTHfl43/lBu3PKsZdEDqvYssMCbttqHgbYWq9fjnf/Ya/3L5q7P9xwNREbbGgMWniCkK7s0/7xL37rs7/5c3/2322tT66+lmzSxiMTob/7Yb/zKy8/mDsESMubNpgboU6bjXOuE4xBQnMM3sOORr03W/3Hl+cf/kf/wd//3T/6J3/8xavpdG6PFLmvQ/MnZ+e//N1P/8bPfPoXmtvu3Q2OEkyxD4Zt4xeO2n/3V//a3/3Xf/S///gnbzlAab/t1Zvff/riP/+Fn/ulp8MXzcv/6+3H0zX791q9Xvdk0DoactA541m7n335Yt45d7rQSglhcXqd61VHyw07HcxmykaqO06x2jZf1hp/ts5xijzN9ull++z8e6dcFbquLdYftVr/6YsPfn227nCVDec3ux1s/+dOjmM9D40SClMOOPaKjvKq5pHFJMFo+KiCYSi/QU+MVux2x+3es6PL+ezLxX6i9sNjnpAUmVtczIH0wOXwhsZiNToVTRhfPxkwLelcgRlHEBKAFYQhAo/AIE5zRitCI0AIDMPXNkWWvqGHpC6J5usqYzVU4mr1f/hfIhoYRB74/VWKwkE6hNJaaMKcTwRKyPStPOI3aQ9UYIjQojk85AuizEu78rLWOFvXe/e15tsmfTL2QdispOSlVnK1EtmRJQyIsDpORgFKSYUCMyrD4LlXHVAlbhtcXzKfM6jv1O9t++TLdocl+N5OxFJkM9Si7Hiy6K5nzBLQI3rdP33day+93JD7fimX96zPYLVI23aUGcZo7KjVeltvckfb+fFJn/b7Zn3eO3p5ef7508tvH3UuVqOWGgX24AgpzHUzHMjdG46dxaRMpB/kEAcpaa13R7O5K5UAtzQvnLdMgl3d3vj4hGMSv5hMXk0mk8W202l9cH7y2XD44X55tBiZe6aEESxSE5wVLaxszPtHP5zXfufu/m4xfXo8/LnTi8/qm+ZmSRfxas9aaMoBF8m3truT5bbNGBibmFvt6Z5dnI1dm7GBNZMQNv6XzX/6w9//lzev5o5U1DgS66xeO2Hh5wZtbLB//ml3+OKTz5999PzZfjOcjB3qXMzYt3HVolxk1VK/dXTS7nW7+93xjq3ZQWTwPiTAZJtUvqad8pUlF62YuIdEHPnCTtx5IZfrgTDsr1Zv/mD+G6PNm+CyODXwLXZFFKNffxSZMBNHOKgwmnXn0Te9cDvoFSbplbDKMaAjRAT0g2dAiET4R8XGJZSc8MRKMlMPpcBXjYy4wttPlKL1f/y3IkcBIUjFnIwYIJDphqny1JZgemf6CoBA0mcovbT/vz+JFqlDg6iRjvf002qD5a53jbzUaw91dhU6V4ESulQp8Gc6xKse8kZNH3U9UVEZuk2Qrb5cc7RbTONULpbsux0qGkyWq1KHatN1ocZlJI5hAdbbUBNNPe/QtTKMTzJLQhXAvt6YDLEdissDwtKo/0K99Z0mg3q7Qas1OO73uPTomJv3OEPU/lXwAnDSjqzS1uIlibCXb+TagRnSD/tj5jmZGYBKHlzDC31ltJAjcR1GxIU7F1l6AnLgIkBwQLO8Djf4gyOe2cwjoQ6kkDhwZlYHcnlAKNhImRWaLJM3HvkCPbh2hpv66a//7r/4pxP2e+/69d3zXX2oZPnPvpMXtfpPNY8/ffF8cMblu/Qu7Lntb98ub+8Zb6Jh1YInp5c1dkh4eEwQLI2ZTr4Re/yaTJUNTSNYLN5PF5MPD0lLdFcsOfaz3cMfLH7rZvMTO1zwGpz8gJgnDZVVr6hhUmSEqQDC+Ci04sCrYAlsOkmjjsHO9MQKybIg3ItjBscxosNL98dv5E1Yg+ISl6SrinrEByaRTjPE7NQLLIcnIsYGRHoI6r82HdM5whChXrhUoTSDNYAOaCO0juUNOhcKA2cT7o8utwPGOSn57kL3HGYBXQQyyqCPIFFSukeGDoNt1BqDoIwCNFerBsd69lu9806HVY+LXZuFH5zzHMdVhDhLIVip5qhK4SmizTQ5u3svwso0B440aNnQxBcYYmVsh1Ue9M/ud6xjpKxfL1YMSWyW3mhS78y5J5gx9xQf62eTjVVVjEVumk0CKUFRUAm7Cw4sBlLC4Ys/4pjCR0DVhtFeSgxQCWZoHjqB5KHr7QAwPNaoYWnQpn6Cn1D4JOuDdZpNiO4WClEEiIFp/e18Or9/eLgbjWZutN0/ffp8eN7m4or73X5EORWrgJyc8IhX7njkKvLm0WDQPUXHYBvcyteLnGAUhVPz5Lw2OLLxSSALAhPtVwKCEsu9SKwOcCVKB+gxEl4MnM1hGMkmmZQ+tGzqi2Z7rW4GMwrKwKtARjxGg4EvzMBRY+WiKVz+tHvCJJIKXuASvjLIu3gOXhCb2DLSypyOaUsi8C+Qdlkj2CFSqiL6WsCR+2YoL7EETPInIw03AhVb/gJwoFL24so3n8osZEKHe+UcgAnsrITkqVAcJdpi0G477O3bx3uqxPpN3LWLUEFZxlcSEAmUJeYfIo7aOnjNPkJak1yo129yw0O332wNmr0ltwatp5xzWejlVAtizAMRJYsjodj+S4V8aZ7bP+K0KMSOJiY5b81Zr90Hi67r9SnD9Yoe59DOlxyKO192Bt1222aeoyjwkQU3HGTEoYI0iW01W5cjEwqep4i2ONG33Wy0O71Wt9vsYMTZhWIqT+htgMaAE5UD1SnEQhNjoRQA9PRB7LgbRCCboaFwRzWnkJFHZbg/pZziSgNZQ4rBEgUR2QAbFe7dZrV5GM+/vrp5mN3jTWV0xM2CnfHVen7jaTCAyQFUnz7woLY/qW1PuPa40+PyCXOESPkh9uHR4PklC3mbR6e1Ht1CSrZq0s/sBTRoEDiCGJY3SwdiBjjErqgiEaYsmr+UOiy3mTfebZsP7m4ML2rf8OITMiaYBr/pEFZdDrJjlAFewQgpmgwQ5sqa2MLvEaeoC4piCkzFTDJ4ChkHzDolGmO3GpQTNno1R3iU0LLM7CPVfAGML975axidxXVwKXSnuz7mcnqDGTABElqTj2grc/pkpHpBgrMLe1YOrjv1eX/fP910z7jbrt6+qjfvvVDCMhUUYCetGBKFeqgVKWxTgNI6Rz9Y8sgJ680th8zXjhrtfvO03e/NN9P5esGhsTHER7gUH+baOBuKGg9VhBMnIarQQ2QIHyTRdh1zBhRaxJZfVtjUbTwTOeO1jd1yM1+tFosm10eRXHYlsMiZ0mQdV++JwfI83yCadTwUC1ScjXZ3xjXu3QGTnmRBk/VZaiMlgHPWoXvoLg/jwwi6qWLWlAm+3Y5TQ1esV5CdzNXAhyiHHHQu/IRQWqSUCqg+rWKZBixfSSxmLGaYThR/nGJKYQFPjbvZvZvO3+43I8a80EFmYOSJR7ke12pHMorTu3e7+YoZFJkiIxu13rDJkAzDnhY2RBptXaIIWYsiALAgQCrzCQMkKLzEnxyKbJG8BNtsOQK4PZq2r+8b7+heEKljYwDEh9AHA1GJg2+R7W+Y8VLwheDP1PoNAotjuCe9+qVXAodI66JzPOUnuBiOBCwqEEByOvEneFoi1tQ9/QMS/7iDnfxLUPPzvQcLOXNwAiipqUiRyRkyA4XVVlY8meQDhspZv0AcyDGrUIE6VHHTqk07teWm3ulzM+6+f1bvXDU77/aNSaNmszWYzjfRmX+Zi1HKx2CPMwg7B1G5zGC/4A5eTvO2rjkedPoMuHjSLg1UZZyxGUrsaHaib9bJiMBQYjysDbG7Dj0kIUyHoa7X9drdrv7cwtyxVogAnmYv83FKPtXWmjF6PBip4BfxyI5l1FuRXJqAy+2qva1314veajVc93t97k2hbmb4oe0KFgKheEoWKMkPeBMvko1qMVeiYrBJCNVA3GNvg7WllsJQNTNe60D0hOBZSeIvVT6gNXsUZ09DjEa4CkWKmi3GdR52e5SMBbTQQtOQdidmXrSUvuZofNNr1oZPLuwTwm1iIQo0UNjEiiPk8x+Raq4SAg3Wn7KpZGIyTHrwsp4z+SwWbG1WvYdZ74Yr7KeN6YJxI5aR4xkztaAwBG+QgFlbpS2ij5zQcJB2IPIJFwLokJwwcGDglyRgpkzHF0OGCgAdgoXpmACKW0IKHk9ifjSrKNpKsFDAQJj54eRzFdQ8MneENkjJsuCedhyDe3pp9zkY0gqILhGcb/4m2MEsRMKFE7kTj1V0BFVkUcVdu75hzXB33xtuNye1Lsst7hr1yd5xFSoplIgyHHpSIQ2KBeqZBbB1KirWClGFsEhUIPb/0r9n3zCTGf0u569sd6yK67hRgPWjVobsaUvVorCnyAdDUkps3Bk6aXCoqetpUFr0k8QRZ6QAMPtnURTCP9l2SDqOWW5kzqaeB8lcZrtqzeA/y/MQUVfVNBocYB8ZULLaVIRGKZ3UMEy/Oz/ARg4F0VFiGeHsfqTdwgHIb45wpAb6zQQFTmiXTNSZRqfzr5Ec8eAxWi+ow2l8njFgVm8ceQ6IC+mJg/hoKS53y/lo1O12mOuQDSYA/CSRnRDUyTGs4o6YKBnSauTBG79AmlM2SlL8suSWm+yxWm17i21/uuyOZt37aXu6pANOuUbrOzSUNfxKKQVUpSey27B+06BZB62m+2A9mMJQYOREwEe2gbYgTGzhmOiAB/aAOfFiNRHpGr6SkREVaFFKxiGkBkYGFHncKdpEVZ6DGUaFU3FIK5a0g0KGRD5WQSXiECojC3DBKhgNYSn+FQaQ4aP8CsDHGptCj+tdltRW3f12WNue7jrn+85drXlf5x6zGgfh0W+z35/xWkGIhixXnrl42RlEWm240V2kFUr1aKcRQVFIGsNmv98cqoGbGbUjp2TMbXwCGYgDL2MLFP/HigYrmlkHaLuUfY5IJGjhbFCdRYFkQ0rUJ0SafE9lKsB4Ryh9kVkg2SHRWtMYZTKOU664GIYY0EdQxcuHRyPNvGiRkjqUsNW2WwjDYroJhfE4GXQ0eW3GSomvzAwUin5lhgp0Rg1mo9OeMorlfgQI+AYXj9+tl1Sfz/eNC9qfbnMhevufwCP5dIl7nAfUHXJVD13t+nbFnWj0cWlSOxbKEAPqZ/UbmgnBRpulQyGq+iEFvPhGxpGB7Q13E2764+XwYdG/X7RmXBzFZRMUtgpHCki02hSPEB5THDgSk+mOlMSnmCObTB6P7sWv+s3gWcAEITZvgjRhAc430l+Cxo+f8EqDkOGQYcOGQ+hJuuvvI83xI0w0meDZN56UmoMjBgKQfQcX7MV6cEw/SK+eDIVNHyOsnvBIcJ0DA4aEV0j8j+TobW2HTDJaw1r9bX9Pfbg5r7XvOadx32C++s7zZFAaVDbO5M2sJpx8Rcz8gnFHa1SZpa6jV+8IEEtErCJnyGCrxczfsNs65lbQJgdwrzlnlh2mKi01Lm1UaKSgYuEqSzNpa449Wp96Ug3khfJDuiU/XiQ10xjfTByQlP86ZKIpQGi9sehgs6FDxjm7rgOit4OqRwkfWOGGDAmk9BXpMXKTN2tgOQiU23wpYPihH2miUEhDlo6F8fCfUb+HJAkUEE9LOuZFF94ikZB2MuebJafyULjRxo0OJRTgS0hU0YMLmJfoNPqr/WY1vt1v1tDBOsLu8IShMJUQvvsG2aAlLs1hLV+xVYoXCkLh0V1tB4vNcLzuj5bdyaI9WzY59tsOAhkXiUsc5qykKBdyWa1LJQvHSFeCGIfJimZ9Rik89qAl0BQOJZgwJDhiMFiESauxVNjSgHvme4aFBkCEklqBecIalsdPACQzkvgos5moTXCJg2OSSJgD49NMgAwZX6NJsIwtvcKscxW8gGSqAgb8B1ig8tGQDNUeSSDj0k89DOEiP1pcWbZf92vt0337otY+2zfeNfZX9e1dbTdXBEkORFJhwLKoTAKF84jR+gGngxn2lliLT5uKCoQ9bbs1N5rhxtlfg3570GIdKzPvuxlbUKkkmTBE8lyc4twUATmnlLFTeG46MvdTFUkBjpAdvDVN/Ecas3Q2XQkPTDR61XAW+aiDmy2LB9FABkH4odSw4snMoCuITJswOo20Ddg5ZH8JF/tzbiREGmmbUjOwOo7+pMmUffklVsLm+8j7ZK4tWoZyGcl1pDHkmRSMlrPRhpkXO8z8gcn1vXYI6RbCDeLeTbfT/YxGOuVIc8CJbN4Z7AV2NJWDWgKFAIE2ky3BVdEE54IlVjgIfWezHczXJ/ero1vUb9Xk6HumIywrSR9fEBS5Ts5qD95EHsgk8OD1CBUAAmVYrTwRLo3la5Awwh4evsEGqUsD3joGDN9DFLKzii5M+pmmxJPwGSzMCWMsAaPP+8BOgiatCY0Z0MCFg9Qk3ohVkEBdYYgwEb3OCVMFwWpKwr2kI2zvhz2YD5Al5iApzXyhfuvgn+LGkg220bW5S4uJqMt6/UV7d9XcvNlu2UUw3zFAykJwKndo8ZWJqGdpukiLI/msr3EIEq3Bj8YnkFQGW9aQUqyzQtvd3Vz32WIFV+ih7bH23gXi1A/hor4leZlEMCMxhfjCJJkXKfYH+LSGWUgUC1UkdnQQVWCohcI0HuChMoLakQu2UsDQOqeAsQ6MxAFMOLKnwfJyWnIUNA4R68uTaeab+acL7kSevoFcKJLNTCF9Zr0hhgSO1yzQQ6EZjGFawkHReG08A8a9V6E77MDgRef44e6CDgEbcItlSo7K8jJCYzZEpBEdaS2U2xYnLTQ+t/3l+uhhMbxd9keLzpyWp539yC9CQ64pSLrjR2vlIpsCEh/MuPvEt4BFkMpNz0i8hgILIxM+o+BbPALgELyKUeB4szBNsyECIMLEJ2C+gco4v/FASRGIoIpbABNHlUFiLY+iEJzELcUiicb7EYrQAYaTYFVYiaiAdKwiwYgVbAIgPAF/4EWGLkgyeAihQYIS8tFhQpb/t6gMW51PzpuroyZr67+crf54NnuzbExWKFmXbQQhkBEXsoxiRngqECNVMznkChAkCQGidCdqWqisPcUWciSllN4IHxsdOQOEOYwlwZ1g9EvtmSmMdBQzYdLKN/kRiSjMyMQGgGC8uKCArKhuowjUR7zJF4FoWvqYYNDTx3UYAweaoOwF0gfNBVCNhi9WfUi3Qxwlm/TTVuWQoOXBOcZwV5sdd3DGrBxY7B+yk5AzPmwbEGcWYUX97MdSPVJ+wUqixECR1NhxVGWz43o/2u90DCRFBCz3KQ8EENQoOaGcBvems95058vBw3xwv+w+LNp2yLfod5DLNzVQnr4nNhCcfMxk+bXcMoxanwZdI1QYTP3hCUHSBbwRUf4IksG1Gza5pzv/QYC/2vUqEYVnAmSohC+QgSSDJBoDB4b4SAJZxePXmjB7KpgxBH3SpFchCMeiM+Fo0EjPIY6EzqAZR8YnZEStF6w9oD24VlEbWcIUUkuawSMlkqK/mR9cwIGZud7wqH9y3h++6OxPO6Nt8+uH+h9drf7gzfQnk8V40906Bc8GHKo2hFalE0eitP2FUHuOGI04u4s4wADIVMHCCri8AZAWKSMxVIbIF5ebRI/RZmo+VeZCVL4SGwETYdJegJMGLAGjI4UgLVKvnz1UhnjK88gNEi9WvjQGTHa9xVgvXdk2ozPMeopQfQRKeDcPQhqm+I+4tYhBl2CfNRHF0I57nbg2frXJhTnm87i2jeFfuML5Tl785Ao+ojW85RF8oDcLotAqMfPPQFGbOVnu6MFCjW5ex9kI9qxhPAUfxzU19r3upr9fMOnXuZ127+b0/eosZ7IMIARInWcxXzAUPqWMYTEmPSI/4puQOsaj3qY54tcY0sKvSfcngldGfhMmDSY+vHRNGgpEwKVLepk5xgXNgSLgxVUIgBLdQ7aVngArBoKG3egCj0IZVYTuQWGVDBEGsHx5JE4Y/nnTl5/KV8z/34/ciVCASE2aKw3EZvBEEb6FYlNZOeNrRunCl4N7XR51/qJ7+nIzvNz3ntY/ONt/stt/97r5M1/sf+/LNcc6fDlejrb0OBhbpcviDJwRx4Cp8wE0AdwUoYt4S7ZjqiLRJV8gqAGQP+oKOnLcWEPlSUURTwkYaQKcHNdIOkIGUONCeJAPsOgjyQkgLEB0g1xJGppoJziQhPQQLlTAuoc2gDtAuUOaLXnNNiFYnR5psIJEL4zXXjBBwhJ4oMhyqIo54oeH3IO82k4XLDbzJKZ8WMQ3st4zMPrtxvnYogm60EBQSzsrF+gTsPmEtmeHHiG/jM8y4mylDYeDpWggA9jMKPU72ycX9Zcfr58e3y2/eLj/erz7cu5RIRQ/ppWGh7TBXH/CwicNFWH+4hkQps9BAK0KkeZgZvryxSvh86fCKoYD/oQRIJzDUD6PeKQjRFYgMzQaKgpSBaN3sQZzbMuEb5ABJcTjN6PT6uiCPxFZJNSqQqd8MqewRfbrdjAQ6NH9EcEhaDHoI1zgCwoCix/oMKr0Cn8d483ASXEWIaU4Kd6GVdbC6t6H4Unr4qPu5Uet42f17sm2fbKtHW06rU3bCf7d8On+yRf1H/1o+8Xd6mo1H21by11nyxpRm0qUO0gslR+EUGuwmdGSSLWyRYjKIIIZVdRmRplSTHOUhi6dFmpCvjjCEpIIHszARXJMXqY7acecBqASJgAzxXypddngF93CXI7GfLRdRWozRJna0H6eMeRLAlzVTa3oeShU1UoFjtAimCM0Pv5ISTxpBiDdcVMF2TfC+qHJYs7d06hLJoEhKdqU6BsHC9iCd3wyJmXVTOlHCaPV2+zWBx2W+bRo0wc+2EbHXf+o/Vosvm1Ph4PNJ8+7n3279cm3dh98NGuv3325u/nD31mNWPED4kKhHAyi8huEhovCl8RX6Qnd0wJtoYpRAaIb0gYrMOgSf+CXOdXXSAgST1GBghWYiIfPgUkijNB6ijxlgoCBPwgIuvEq9EhbgcSlkGSTC3cbBBlJeBmTxAZasjiaLofEYkgeSHL1pPngkoRWnpG1WgrHNEliOIWxih5LRKsPhgMU/pqhOwH4hhIInGGREdoYJgw9aDaPzrpPPho8+aB79qzZe0qdyCr/7X5Am27VaG86/eXJxeYFWBrt3lftZzfNh2UDPRwx179trTbdGp3GLYuzOK/BcXiFt9Vlt4AnCzIiymFSFs2kki8UhJmWmO1PWqRMjdM8oz4EJADSkNlXeFARDsmCpDWBccn0hcAq4qi67WEHZ3wZsGS4hBNdSGvEDjg4Qg/twEadBsmssQCCGBhStQkNEMjIW9xDOggUsfuLrwQGCUENNdCS6dHFypV8lm94GU0ewXvC6R5Oi9I/lhXgITzeoYGZLbTh6Y+y6dLthXg64ejCGxfVz7vN6fnZ9sMPuz/1ae8736l99On8/Pm83xs9vJleHc1pUUdlLd5AqyGjyaj4Hkg39Sna4RrZUZJF0ePZk6CIzkVAAQycJPo1zSHqgT54ILrIgsIkQE16Jj9wJVEEwxGcAATSwCusvBB7/PstMAUs3EEKVASFRls6B/BCmwstwpEvOzsjGVnsSswhp0R/YE2aD1bA8D48B0tliALBOIybJ1Mahsqp8pXY4lYMJUyECxSqX4Tdsdr55LL39OURSnj+tN27aLSOd3XOPRrQ5GRMBUmYbzbsXuWoomWnszs/arZm/bPd5fDksnnZeLtc/+Rh+WbWGi17mxXHarj2Uj1E4NgSTBPLlVdsYDXrfWAAb3JFF9MXBzIyNhN5qW+A6lNxBbOOkRMiCoBHQ4XGrLEF6vAGYzOHylCRAoERBHOixxfoVcKsBmn4lbYfWgiwIewLMUjKu1VV/cv8OJAQVj5s22WP8nozXS3na24MLY0Mir6YF61/2ut978WHi+36995+RUVpSqRFLQ9qmblhRdO8uW6z22zXYmsFd7o2d536une0fP5h/bOPep9/MPjWy/2nH62ffbA+uVz0BktWJq0G+1aPFb1U7pYqPsFeqQ1exzc9dKoe+RDm5HVaLICCP5l2WSYn4tXimwD+6GJehSncA2FiDv0VIsCAVr0Dg5/IBZMPfwWgdeKfxvS0RRJhLf+SAAsG4A1Kr4++rkGtSMQtrO0bxwBINh8mfsTmE7kW2IMD73NBJhXHdE6wCBYfMPBWQfDVSIz8hHvErpE3/qFMarS+73jwzcSEVTBeNl2dnHXRwKcfDM+et/scVXLC+aD7PTcrd2P1I1UJ24sWU1qgi+l0MVmsnIjushfh4sn559/r9Y537+6WP3mz+tHV8o/Hnat5Z75tbunBsKOOFWwux2Rkne5fSkimJpIisSQPflFJcfimVwdUiQvqMrmmJqoLfg0APG/1AEPWCGndUflGNU9nMJWQijDrN7yjrpPRoYZ24V3pY2Voc9SD0nhcHQRGPWkpUaXSWnRtDTuQ6KeVKhQkJin5GCsWNiwRWk4XiwWjM1GRgsDZOeYD6/XvnD/9M59/n93Eq8buD7780gZmxABplD4YiYATs2e1VdczZjrN4XHz4kX7g0/6n302/Pbn7U+f71+cLy+frE7Pt4MBWrdibIaDe9mi1mmzcsJuQBAdaSutaejTLRXS1GB574E+Io5v+mTJoUsmqzJgVT2LFVMIj2xMTZH6QCUu//MBzDxWKHWMX0xhiaC4BkBUoeEcCPjEb4G0IIhsDt0LnQTAS18MTj880fMNAGODWppkevvwPRjCoVjDK5nyyBoTlUDxPYTFFvG975lwJqkKlL5pPZgLwkx55ZosM2+GR93L58dPXg5Pn7V75/XGCdsAd/sOL8sNWXpF6U5hu1yt5os5GjiejWez8XI+Z6nocN9+dtTvfnhe//ik8Z3j+r9zsfzx/eLHD83X68ZdvTHeNKZz5h8ompguZGfiA9fC1PYT9lHVG4gdC4eRTsQ4Ruxd+o3WQiD6iTpFWy4Zh7DiDOwhpSQ6S9uSOHzCRZYkCwlgm5LMQBFdQLaqbTkNGEyMG8USNuLK6T+4nxrINCe3inNUgGd2mL/GC7zVIscIoKjKi2JVMiyyJ8sW49lzvMdsxZqzBWfwOOkeb/b6hvXmk+Pz4fCk0et99snnt6PRdDRuIj7G4bYmHsaF2IfVOxkeXzw/fvbR8Ucf9z75vPvi4/qLp+vnF7Pz481wuOn23a1fb6B9lFzedd1gk0mzzjxvTHaCBzJNk6ZCKVZpDXb64R/iePLLr4qiTSEOuh+/UBiOjF6lIbyyuRhVUwQ3PgDSXH2Li86ELap4MOibccVvQFVIkrqMEXOUr0kb/FYno1AQgymFDJFrjXrSVSDWhO89khCPhmRHGPjgUjK08pIZ1SOJhyfdjcjHL+Z8/5Q5gay7D9ABqTWDJLZ+r33+bHj5cnhiK9RFxYyScC4Da18Yaue2u9Vyye7U6XTGkRDj0f3D7cPD9Xwy2qzn3f4x227ZfLtluRfL3066u+YFKt14eVkbNWuzYXPWac04W7FBOc+unuZqTuuW8Qa6iaeM0S/ns/Hkfny3Hd30pncz9CRkEd3LUcuQd1MKh2AoViQKQ5UmuF6ekB/M+KiZaY1ssrtPlczFE3GODZNvnlMZJWTWrMCGnBIu9NDRmiKwYFLOnFSMPIq5T/Yo0tvXwTc9iJn5EIdktrP5Ygyn6BISbTQ1K2rrvWb7uM/JAWyxaj05u/zgs08e5g9NToja7FhnRJnd73V6T54PP/h0+PJF/8XH7csPG+eX+4uLxenx5qi/Gva2LQ8WRf2kKcsgk+phZp6zw9IfGjVoPH75IrqWcCVBwR5J9sGRL8TlQ1oPhqQ4vildqQCKYkpOfGGzQVhlhHN6kTdh1r2CJBaR6KBJgyZ+4G04hG+4BIxeB/hwTmXD2dKqqF/qoTqJi3hi6EyLy6LUw9DMGJgJIPJXUiL1Zl8+wRqMjy6Vj4grsPw1LI48gbAYqvJJ53QPag7mBCtBpC/elOIw1rq99sXLwdMP+tSB/ROWvLAqhneLVLCBlt17qw2nWk5Ho/Hd+P7m4e7m5vb63Xh0u2achSqFpdG7XM0GDxx1cQXcUXvPMdxPj1vNF43mZa82bO97TS7Spqh3YamtNAYNN6vtcr4YPYxev/5x7Yf/cvEH/3rxcAdPgaK+Y+bDRBU2wD8fGBa5jHuqos6YU1fTULEh0yrXeIzVs3N4Nk3vcTHgexoe0SCtRA+yBoLMMDHlAK1IHGicggcxdwVClVmZLXzJ8IKJKNbr9WyxfJhOZhtW58GgzHmJR1L7zXafPcq0dOudQedo+Oz5onPS6/dc3soUJJfrnlz2n37Wffmd/fn57OS0dvpsPzjbDXo7LniBPEeeS7xoHVKGOtIsVh/BDpnoMRU5gJJbvkF7sUpHPqmBmJOz6ZjmECF9sIaAAaNEYX3fEGF1hAEHd8yVe8KXIFU8BYnIAzACkirSlS7CBzHhH5EGJaQIdxsckHSIMQxZzRgqVBRstn0UcpU8mqORv8m6RASwjEwuEFSLn2IUWTxJTcX1QhpgAIQXhnzTCgU8uvANasKui8gjVLrwjThZNNWlrB0++XRw+rzTv+SOkN2uy/p9+1Db1ZLLFlYOLyym84f7+/vr++s3V+9ev7m/vaJqJNspcMh1j3EiauoB1gqjGjSHuv1t72zX5jiNT+ZHH2075636EaDWMKgpo4ccNLvmgIzNcjpfcNhKv9Odvu69+cPu+A7ueRhUqUCkmqRAbXJLxpo4ojHOQ5rwJztwyXSF6Dv9TdJxZJOea1d2jTUbElgRzUwgvSgU0fBklHhCjRDeGJvh1Ef1kJaXcSPNmQlYCUPGhqQFURkhEGY70XAiwHIyn002c9epRLMOAngIS7voqN3tdLpgrnMIwKbX7w72gwZlVqNDm3K36w/2Zxe1J5eby6Pa5dH++Kw5PKl3uXccDZUjUURUBAVHQCtykwAIBQS3MSOnKqg/mvwJpzADHTzS3ZDvfcNHngRCvYIzaS05oSV80hdzGFADyOMpBrInrOl7CKJbBIlA4pH5waCigpHZhbTAINIqRkOFIyEw+IbByjDdSWc4yatKUb/RHBVd4cA3DRFeQaviSLCMQ8egNbyViYyv+BplIUVDZdZQWXFHEkEf8YgGs/V196h9+cHg2SeD0w97wydsP2K5IXutkdY1N5lTTdEI5ZyvyWI6Wt7dPFy9e/f261c3794uZ5zY5MCEJHMCLV09+kwu8lYSYhcCY+vDffe03h/suu2FG2o79SZHYnY9eswTI+tsLGYL+7o/t1Z8N6j1h40Bhw9yxIyTXLxVtko2SUEtQ6pIltkdCU2YsJkovExcMj/YadjUZzqkruBmDYHVoCP+HH/jYAuTFgy/yNZoglJr2CekX+bADOhkI3Li0IeDM5wVRy0GqthZgppLiqLHX8xMTBbL+zlHlHL3ZpTFSr7EA8gqWQ4X7FR1WrvV7K9brE7l/ELUhNPB6X5DJtfIM6BrYvesAWVDR7QnrW9Uu6DJaC3mjZsVM7RF5Tzca3BLcoO1TBzXLZv8Hl5AoSLe/JVdASZSLT6PzA2GAqlPMB0qH31Nb3kNBlBYnXuJfMjM8JtUxvdgxZBowQkNxR2n2GmRCC1FAmcSaRbhgAtZFK3TRGLYcMc7yctQUT3qx1igECY+n0PqM3EHd9FLjU8EyeIz0emWRIdvxprwpfDPgueQsCQdPJYYBaGpxYEim8qr3W8MTnunL4/OP+6dP+n0zuqt0+2+teSeyRXTedR/Mz5UgLP5dPIwe7iZ3bwdvXn15u3r19MJa1pY0FH3GBSyv4UKQkVktTHwIK1wUzFGfGBdUMcHSpB2a7nVniEZTpdhUtxVKl7M3ek1OYSCgww5mbQUGlIczDikVYnCLbhN8SeTtFdvwJsR4UBEVj4AM/Co4lFjY6IKZGk2WYT2WRmigdF7UAmR9Bh34dtk+wJHBtAsZ/UaKoegNhkgVhPpWTJ6SsdSuahEg0Eb9kxsmB5cjFezOavlJEOUyRrIYNSVKr/V8mBu6OLMrSFjmrbMmQREn9n14So7BqE5zMctHGgxKxeMxoTb+hSZYR//Ta7yBsEc+8Og2qZ14TWQzkriasjyYAhEB6s+kJUaeADDEbAQIcKCGR/FL5iuPcyQkuaEtGgIB0Ka3/Ayg6Rj4ARehIGhRJHII6wgQYNxFVy6WJLpV6LTEFZGqQ+8lyT8k2zgMSCRxuV/LB8hryN6wkpBoEicElpZdQ+rsQRYITdkLSF1KSFFlelMnOmcZoMf1A8zGEhMvbVrH+27F53e097RaYfjKI7pjVy0qK8okXctTm5ZrtaLxXq+mKyWowUKOF5MRpP7u+nNu+mbL+/ffHU1emAGoc5BRKSOWgXULGlEMo0g2AMBJlf5x5vzcOnfsTdDLYhEES54hESrpzCuwc0pddSvqxJGC1CVrsg3WZE0w2EOBpBu26h800U61DT5kbmc2YF3ogIbfbu4GZ7VlExxckYUNSEvGmhBKVpFgHCQFcujKQ9a9N7Y4EtK2eCEDypiUxSRF8rY4yFO/WhFc+zxFt5NaMm7OMaalFKAb8poq97sevxUDOpE1cqJOL1d3NdK60D6UWwKNepqaGP9NqrI0eAleBAX6ZPUSH0RtKCeY636Z4PzD3ez728mbN54xZSH7bQIJqEmsvpWv2LiCfc0Yn7k8ntehbMZMPFE0Ef8YfVTEGkMKk2YD5jzN3/C5TEdAId7SVwiIe0Z6pCQgAHyGzwItH8yeGQTkTLCETkMUGAJ4DAXkwSXZFQJM5YqJn4RSax8xJChIMuqJxzDJYMYVfUKGi8uW25k6jxvHn3cP3rWG54N+iccYeahaB6JNESnVuv90vbnfrFYUo7P5xTlY5pUo/GYk26v381ffzF59cX16G66oXVUev0hGVEluiUJpUN+kNGcMzVu9NJVyvg5zSqx0cYMiUSCbQIijXQgGe3gLlI3j7cxemB/bPdGzSJxqlawoQiMqEva9aqsURvIFaB1z7IYPkEE85OSwrngjF2yoBvZ5twaB0ydHnEJeYoYoYnEFmnqIUun2ZJAzUcv1ogCPYJNrHxo9hqdETr6umW59ni5uGfexq0S7oGgbSVKKxtftoj1O1xlzcJYtBbS2NLV7m/bD3CwkE3pQ1OXxQXs+/KVs8Zl5CYsKayM6a4zhVmz2+qfDy+/1dyuZ3fD1fTH29X1djtu7KfNWJBrWQClRZUNeiAf3GAmjqItEZeJDTlOMMvFjC/cDaKDjwHzC5IkUpa8B5CgOOFbeWHLwIaNiCqH9wLiBMGHIIGhUJXQfP+046OLvKOVXyKWfZihMriA0agibr3ywUBSMyV4EndEnwaDRyABSoDygzV11TgzFF+luL7rnDSOvnt0+b3h2UftwWmb88fo87S7DNxx9skKzVmtOeOTU8248JzO3mIxoQW6mk0nk9Hd7cP11eLrL6Zf/ZhhmfmSqTUuUAMr54KGLhFbk5OVGBB08NFDWTw20LWfrjJGltEFwiCypolf3GlyOczPmBWDjy7NQq9r7BLg6mZqQgffbT3yZmL9iSdkQJ6RwtDmQ20pz0hpdAUT1kD8x0ukVpIEUeOQbjf6ujko6jiAbCIrg6Amt/naFkUJuW6auW9GaMRJv8xKkIarNaVXNjrIlJJrZCjqlnGs8Ww6Wk05YI4TjeFCVIOOXoKdAmdAe7HHDdyUCfDDMWAG7uglW41KBnjsbXI6z44DepjSBMa+NrTHsT5SSKJMXdAqsZpMA2Q3672j1snLAZV7/2gz+3A95w5SJn3Gu+XNfn3d2F41dg/sXDZY4DDGfBJRSA4OssyfMASMcpVPeGn9Ey7hm+58D56HUBoilJ+Dd7qE1U+6H4InPWFN3xJvBWBE1fuIP1y08gQkNaEmkpTanLFkkhOIb/ICR1kJRDZ2D7gCUaIrASlDzQzZHz/vkZKhoq3G7rL64MXg8qeGT74/PP925+hZrTO0oLWsrTHzvt7OtqspR3w6Db9YrJYzzvlczmbL2XQ2vR/dj2+vZ2++nn31k/ubO+adPamJeN2Hx4w3CZNcFoYqfEg26kfDLnpZIfAopWkPgpQxNUV6xZEST8Id8I+BEObPGLqnPlTkYVikAKOPaELHCIYh+AFeYfCBGeGiJYGTN5jxCv2UEGgi4ahKg1Yj85kM09C1oyWNkJMWNIEaXsJIIUpIR9UjuquptyA4kyITmMhHp4AHMWkyQldsL5ygX2zpVquEAR5rYgOI0Z5em9NQXSpAOYC2gcnRMbRnw95fywI7qBAKLnYPAkNTmMrQ9a6hZqW0yAhhJvGS9mgCRNB9kyvRzxqDBueJ9IbP95uZmszhp6vpcnq3nHy9nv5xbf3Hjd1IioOVpji4ll85COXJ5eqbLrI3WJxZmslLx3SRy5Hm4mUMESQdAy2J/tOOBT6AC8IAznj1DWvB/57Xwb2QlBgiOlxKipw3ZbYrxAQAHjyCeZpBQY7nk79+KwmCu+VJQ7KscgvAEGpM0lLE0wwhnYhHs9s5+6x/+dNHTz/rnX6nNXzZ6J7uGZzkgJM1K0BnNMvY6cYSf6b7HIiZswptuVIJGRCdT0eLm5vpu7fT119Nru9XE478ZUCCGQ0npkwFFwRFtLWOo6MU3uScNZ0igcg4F85LYc9lF4i5JboQEFdo5YcF3nCIHartXatX7x+3j4fNngfnO3gZ7ddkGFgjlYVZoMA9WJ24Mnc0F4hgc8DIe0hDsKm2oIChk9qqtet2TbmtUk+Mg9goMEIprN+yMmTluUrI+AyPAzloRCqz4Bk/oKaDpHLfxmy14sJGjntkI0hGii+n1rBLiwcl7HNecru5od3vrpI1nThaJT3OemI+llFRDnmyw0nDlkKOhWsulOBmqFi6zbipZcN7CTTmYGaVWsbA6p0N5x/sOUzkrDk4bjPw2mLzBczf7FhF9/DJ6O3Hi7sn28Xv1rfvaPKXLAzyTEnkLFiTuQdf+YtvZJ2GSLqO7xsqgARWDmVMCZVW7BgKtgBIGL9YDRHIw4TL+5Dp9Rh7BVNcMmAEMVQV1iQgYt0jGxvKb8bhr4/p9EeDv6F76RgZq6PY/C9mXSpzyYwE/RNxA9MZtE+/13/yg97p562Tj9v9l5xXwTUIZC9HhaJ3Gybh7Rrxek8C4wnrxW7FIURzOjSL2Wj+cDu5eTu9ejW5vplP157JS+8EKqHQAYkYKkDOSBZjmaxy8dBKJNHSHJpIDBY3u7F5Qj+3zinAkBbe9idBBT5rQjakdgYcSNw6Ou/0mBOTDVWOE5VpDhdQm2DsBA0A8lQ0MkgwvBIgIYtrlIFqBeOMLGFpejHEiuMfmN+UTIqIwFhIgzKQ0BxV5qkMmbLn+imln5IhtdU6niLduOKNVTKLzXbCDCFH+FvkPCZBDbAvxkRgZ9AfYqDlQUZwEx3rbmLpTYulSRwGZxtFMSUW7v+EabCcvmiWX3gFJ0AcyE2bsUe6rVHpdxML2y12rFtVmjr9BhOSvTYHVLL8u7Vftk9fto6ej18dz696m+lv1TavSbz44vUDvogEcxoSv/zBIXgtFe+9guEr4cVdcxSggoV7wpijWURnEL4iLREBLMvC8RFV4gwwvQxQYArOyiXdFYkECzwlaRR/x6e0OOhLRfgA0sQT4dMYaZelBosnXbRlNOGYnsUx+K8z9og4owdDoz1onv751tlfbB39dGv4cY2FoK0uzo5IcOIl2rZmeo6XxTCcNk3Lxw3eG9bhz7dLVv5P5g8Po6u3d+9eT2/e0I5hhWc9TuYyzxlAYMMRE4RVMmnX7WP3OIJFTRhaZWWCOq5iEN9tBw7XwIRkoCQaOkBDZ5H1TrfWO2n1L9rdAQV6pokwakRwJZNYcc3Yg6MJmVw4cMQwgUFVRx6yGsQc4kzNB812DWmRtqxtbJRCmnkMDhU9x2aoupy4985ND1+j8gLCNMR/FAhAqo8wcrdc7yjEFlsuj/dJb9hFv5calOQyDQieFf1vzjrbs4CI69M4dZ5CjK3DFGGsAIUM2EUzArVmPREqv7EXKgLbERlvlU5/0ykiNCRwDDhtVzOD0GTpcLg+lW2vwUXoFCnd00HnpNUedLut+e1qM5vWdvfmGayUAYG+JDDMpPTgbqqNRxoDUj4o9aat0IE5XPAyh3grq7+J4eAeLropDuajiPwRG5jF8F6osATOIkYF0FgiUEahNYJnWHiEodUfxDwqpAeofmHgUyQoAYO1B6MVo2JaAUcZdQiYCApCfoIvgJrm5qB+/IPG6V9pHv+Zeu+DRu+Sspwhb1pfnDC0Wc1YJ8b4OQfjejWyvUFqQs59RzdXbECdT2cMiT7cPNy8G9++W40XcRqwdTnUGG1GQjzxSBbVAhOK9gl1YmEHGkq5A02U4orRQQNNja06cNEDc5s4U2GqIvUBU2e9o0b/tN07ilEb1EJoPlYSGB+fzB3skXPhHqzSFHCGCmILg8M98UTLDHlfbrYMCW/WKGFZTirBESfSmkMxjs2ghxx4YXMUtbWaJ1aqbkuZ/DoYBK20KxgapQTj0rhkDjqPn8pY6GRFGcOxW9Y+0Cnts3hhyAXKzSmDJ/vRbD9hU3Rjx8wPYzYMnVALOolCO5nyChbLOoUrUpj6XSUWQopRHaCfy6LxOQWf2gHB8JZhZxq6Fig2r/sMb1PoDear8UNt/Xt1Tl2O7dYyoIqE2FStQFzcIxmSEDAF8mBOEg4YIngCI5yZ5RlEtMEgdd/g/MfXVGIgMTl6wE8VMOEr5NATkEFhIDcKyy4Dii+yMRCHGDIGb28/0iOvQrVMWzzmz8H0nrk4plfyQuRVmGIKrEloBLC65aDC4U/XT36pefqD5vDjffPY/bRRzFI6rjkZhpPrPW9hyrpphg/YmMTCULqC7LmhFcpS6ulkfH9/d3M1vrna3E8bD/udt1QYVSEKE4yi4E5yaJqihMgeyixrkE6KfspuGUP7il0LqiIKGRLJuBwLZlCZCA7X0EKKQfoy3Cre4ban0273xMN6rXHL3GKCoooRY+SSZpkcWhqZ+KiQ6SgwpPAAA13kS76OSNIbI+WrVocyaLVudmEc4t4ueUsY5LqMzTBAuqZb6KQ9e7BA5uIVpBhhtloDErlhkJkTnGiLooTQTRSoSVSAsiOVFVBatI7IsB7CJaKsIaI3zJ1W89vFfDKY7k/2bUWJ1ihAFJuePMrEStSQVpR2p2W7/7Yz0iJN/mVSvUiDyni9yIKPpK9lL6ot1R5b2uo0B+cO7LY2nS51d7u+/u36fgx7YFMihXeKfWLNaCJWIzH+4gUd5T24h2cGDEoDMhUDI/Ca4RgmvuFiWKM9uFByhPIFAL7KEtpFggJKi3iwpT7nUIOyFgGlLnIasINaMoYmKVFcgdI88TFBGtM93dKpmOPH+DXkf4SRZgETAz5kM4fjmYbO2b730/Xjn2+c/ux+8GGtfcISZMinwcPyl+VytFqMt4s567HJYL6LxYy+OitD6QWuJlMHYxheH41YnnZzPb8f7x82tVGcOxQRG61F8KEc0Vhkn5qFUR7kRBYXGLkVjMzKEDFC+OJQFVeCIKJgRQHdwe52HJbAMWnZO+r2hhyx1GQYKEQBoOABaDUELwxILFoKG+RIABi3lrCSF2h+komZkoD6KwwO7dIUaDFTsdlwI0Nj06Ev5fkb1B8c9EQ4lNCNeXm9JsedUU5EcpUg/KnWQr9ESHOefvZmCh9jC2/Q6bSEneeoNBn/HDApRK/QAyO9fbHHQFSdM0DnNw/3r0c3D+eL4UWXsWWwMVlPh5BjDFzlzsDMnqYEtBcNTDYUGTAHNCYraF5zldViOaNzTzfQBhjDq4sxd3ZDTSy663D9MkPc3JHYZqyO61jvW7XxqL7/Ud1jnp1VUtMlu3qS6X7NeJmc3n5TT3A1L2Q7YOkbvxr51xFIfKzO1Rxq94TUFcfIyuKSIaMMCRIM54O3wSOg5qwMtUJw6KS+thj0FbGOUXAxDXzg0cEgTkQjkWspj8ngCakJgyEifeDNVEpMPuDHK2giBItOOMX+282j79WOv7/vfsA4HH0dJqRp/ywXD8vZA0uwaXdipTPE2bOoHBuIFhPuzGMN9YyW6JpB0cl0zMTEzXJ0z33O9ZHj7DAtyPArF7y01/0NHpGCE2LNoAItW9AiwNG8p6YjN+32UarbLbSYcrgB1ooCOSNdiDG1IKkQFimh1dRm2XenN2TGEOToT6QvU4zZp2ID1uRfOkMIXpqDqDD5sXAPIrVks5axDqow1nNTCq0XnU2XWU5WzHHhLj1DTwh2hgCS1KGigbFricWuVPUwlVqTqkrZIJutCFnpyZET3EPIEDOMCDZlpCAiFUzMs2S21+h1kP06R5F3j3o91gCAZTydfnlz/Wr8mp1dnRV7wLZsLLRCpulrTxW++ZY6UB5EKk2u3NH6voFpp+VqNhlvJ6M+R8c6H0mK1tvlZOmC/EV3d+6Nq6SLy4CcAv28TalQX+wmHJ/xB3UvsyNfIr+NIQzJUGNJBkeUSgJW/s1JSE6yFMj0j8BBoTSmI+AqTGiIvRt8UhwOI9MFUBnJsP5SasZjbDzhJapiCIRJASqRGPQTg29U786cVVgiWcUSP5EWgx4UL5wJIJ8jIEJk+OC7ngGpjxHli9iwz+WTev/btf53911aoUMSibC4DZCZ99ktTdAYzTMAiz9oe45HD5Pbe1TR0QlmLGiUzpexQm32wM6+eX1CTy/wlzijIhiw281VyPQwFFSIokbrIJfMUjBTCE+tICh4GVqxtEOAbI7St4lFWHytDz0DSoapRwgxgUgkzSXEotNrd/px6wJ9IxtFkUb1DfjgFi4GDQ4TDHPyHrd8iLY44pXAZL1O9i3VSSbxVDEUxsZ5d9NdcZVUzctxWsq91U5sojIp2Sdkvt55GXXPTpdFGWZ7IdEyp9qKwywWJBUVzoUBwKOBRAQuTlXt1RrccH05OD5j87Tz9XUKvuvRzVcPt3eLWXPWOF4tjiKCSKiXtDE2k0OmIX8hsPI0njBokRFEJW9ojFAZjx7uFrdXJ4Nj/Gw3MwNilk+YMGT8iPnR7vCs2T2SsS3OASJdlI+TPYM0my8dAg+ccCnYHPzSqSrx0zn5inwHhylJeZIMM1PHKCMx4R6sFwA3S2Ye2yNWuQ7IhARk8MRnkkowfpWzcAhHUfsKH+5a6dSENeHCPegRQDcaN9jTUjy0+4glkuQv/5GSKEsiHcVBH70C2ABpDCsYotxsbtsfNvqfN4bfrQ2/U+s82Tf7hCAPF9SBUy/aRHLATHIoy2mb2vG7vRnfP9AtdO8RkwnL7XS6fLibPtwtR5PdeF2bOHpNxhMzbPSE7KG3msUBoeqevMaXcpNa0ZqQmX9KNgvSmMiOQi/GZhxqRxUxI1l5MwuER3DbTDBQLHSdXYXCfaC9DqM05JilmOkOnmsgunjSWGV2ZHe4p798Bd8BHgNBszwFAgPFgCWBkysMDnssKLUFEk/DgXPAYZyNIflleRLrZlhAyqaQGTN+aCGZ7vhq8I3IUBYnJ5hm3cztmBgdzkRF0tw20WOsrNY57wyeHx9dHA/6PSbxOE17PZpOXt2zM3o0pwBY1dfLBUpCfBRIFFW2RZ1HYZ6QfJAT5pNM8JuJghJjKy4Uupv5Ynp/dzt++3Y1nOJOkVhvOQqLrDM2t1+NOayGDmmz1+fm8/2Ok4JtAje4Kptbg6ez2u6dHIqyLTBnRGl8VMhwBaVRo0N48FEVIYhsgf/Q5a9clES9BNVJIwAZPCEMbkC/wbvM2wxbhYnoCkCmWxdg4tWQqDOSyjHj8xj8BH2EqeAOqRVbpjy+BsEQ38RiijIa7ZHV0Q6mBbixR/+tZuezWu+n6t0P6+1TJJrMWy0eFlP23TJ1DG/oaTArRuHr3tzb67u76zuGYdjbZ39h31ywE3zMnc5sGtyOFrsxauntYBQhLPjY9/ZIEldeU6h7bjRaJz3BZkp6zFyjEnIMTdiRPZ3RNNqpcZavM2c2Sl1EKlpEQkBe/wkBEc4WUhm2WN3sxlR5hJ4EqwAyxtAMi5J8kmEHJh2chQ7klRe4VMK0ovPUVBT4aBvVGZMrTpdu1o01PcM2KmWfMDscIIoRRTur3PtrvcE+R8oUW7kgDc3wAN/5auOwFk3yICIKJkzwqj6sNc5q/Q86Fx+dP708Pen32c/Fub1MFS2uRqOvp/cPXITK+j1O0Vou2cDJOWyWICg5RG1ZvEYpiZpHS54EmAhngfilMNAcXNRoG3ZHg4YSll3X6/nc0SO2RHaIk2W5XdYDAe0K8/ldozugWOHwrd2eHdnP6tufbqzutpub2pyu+ChiKew0g1Lwkq1JwsFsKoMayVAZC3RILxYd4/v40QufKOBxFbmZC1dNd8ILUNKGh09AGCxs5aey6xkx4lnhMlwxI1mO3JNpSGCE1y8MEIzBmoP4C+7ABToAwqVy1oo52G4o28C86mFz3XnZ6HxU635r3/uw3rnYNTgYhv2knAJzu1zYCkXCGWdwWG7JloiH63fvrt+9XUwmJJlzgdAgty3NZqP7yf3D/H62HXN1coxhDJAhj7tvcG5adAW92dOGnPQGnyqm2KZxvo3mVPTzZEaS6HSzZblWNNDg0XZQU6GK8jAqQ/jgnvD4OuaIb+SHrCJkfKtPYY/8CP7IRfy++RQXwh44l9lEVqQeUhlyt4M9w+V8teQeUW7Doc7Jbph9F18qQ2Yp2LXlNqtugxVpyr6yZtub1EZ3mJUOc7RZki0jgn54RcnFzTq9l73zDy+eXpwdWweCzUqJJabzN5OHq/VsyfJydIPKmR47k5bwkDuEqCii924nIkZlMqV8TW2pAQEiNlykyQpamqlF6xSpjL212ywSprvbYQ1bg63bPc4TJrtZ3b1aTm85iobNbIz67rgAePu0fvwztc2b/e52v/yhJ78GXr/5mANVlmsKPdKga9Z+xT8gI8cM8v6TAl+5ZFUIxYfcM5/lbGBVt01PeR7BUlmwZw4nfGbtwcUyyicd+La8QYDcgZlJc8YREMiycPmNYNrDqleYtUpePIEiNdDcseIZ7nsvW+1nu86zeud012BVGXkwGz+8W81GyDhrrsgKRgPpLUzH06ur66u3b/EGN3tquizjpme0XI5H09vb2e2IBR8Mkym5Q7af1upHNEG9QM+OjRnoq+iHaEgVZh6UUImkzg3ypRxPRRT1o/iBUL48yDBqaYWhmOEUoKLIlimJRks2RIFvcgKUB9apP4SqGEY0aU6DaIQ2YBBSbOEQ7oHT3MA7WqToHXOcy3bfAePWetNkfzEdCMaFmO1zLy3JZhijY2WIKjLOwQS6vS9Wf1PqeNEIbdr5kqkJTktllFrd4xBk1O+41jttDZ/2j5+cHp8eDfu0Z6ntjXjD6sB3k8kbVsjTTyMM+RTtFAeZWUlnR1IlJJroY9opiCQTmNfHBKt4JCUsepgstl4dnVwcP3m2mNyNxyNWn9dQRc4Q7g1bA7aL0aNne2SdueLZw9V2e9nvD+kD7NuDevdpffBTtfUXe+rDzauo08RdRfho+KZmVKQErB8p89dPIdZfnMtTOeJ0QFWAK68KRxUkkYaKFkh8AtjPN6ERKNAe3DDwtrwNxFITP3hWFEoVq1rFQsVTfvEjY+gZYMdwiCZVEXm2AowvpXbzpFF7uqs/aXD9fHPIdPJyMbu/ez0bX1G8cmJQp83dtI35ghNPJrfXNzdv3nK2Fz0yLnMfDo+onx0mnS0fHiZ37NxdbKdseI9+yfGudrbfn6CHnMgUGhgjLdBjEii9bVwGK7AiRIzv2F8izVZjwkBjKBruyKorSBFT1VUguYdGkbqSQKXMYUAazXUah0UJBQsQw2DKeqxEK5PMRz0Sq2aZ/H7+ZtGZAHwRVV5rHld10thmSIk9XAtqju2ctjC3Re1qbWo1WsSRbbaTHSZtcEdntOgoOUxJZCiK6OTEasHIFCmIoqp5VGOPJue3Hp0OhqccX8FIJRdJUBRCHQ2SzeZhNn0zub9Zc1fEigLMfSMgXNGvjm0T5q6aLj9JjQPt+JNQ35QxvElLmoPXVixwnuHQwenZ0w+/tZieUbLOV9OHey5DZ5/hUWswbPXrHeo9mczO0cVk8265POkxHE2NX+83Wpf11of11tP65h1lS0o5sbxvSG5DlgRFCSodh+dgqQz+hjkd+GZKgvT0LA4VYKn/In2R3Cr+xBBgpjsDG3N6VGhKQCJKIeCLEloHWEiFVPJbkJkCXh4/FQqNuCM4IaHKasDonhhSCb2zhBZgfdsZNndHDY9vGdK6ZI7o4f7t+P4NCzN61HMd765eL5bTKZlxx7EUE4Y+d5xV2T85Peu0u+wYnM9dpHbHkMyENpXXAxIPtd9prXZe47sfese0WwEQAIZeLAJCf+hZWbwEXWQrwkXZrbgoDA6QIuQ4sye1muayrxWKSqoUJlJmQaPJRFIJurSZ8Qkvpje5qU7BRvzLk/zji65DSXIrYAwimvL1B2t84xOsxBQv+RC6RP/QUcX1uuMx/6w9a/RYXsoChTaXrETvz70U1ITsxmUfIKth6XyhgKoIfbD9br5kOGSBXEczoTms9V70n35w9uTYBmCT+o+ZF7bS27JFdOjnrTZ3s+U1h9DsFrtmnCNMTUhil7QTHawGMfboxaqKVbkRMqT4yDoShl95QmQQXpzRQiaH+8OTwcnx9oI+xi04GS9t9XvNXo/FwyyYwUyVvqLxC6+XN/MOotLuUvJsOYnrtLE7bnJ1HUoobyMbzJx4gosRd3K+yHI4CyBg5EFAB6OlMzz4hsFyOsD8aCYResUnPTIfE3lBV1kAEKbCQYRhTHuxJIzfIgoMbUQSwOWvnArQQm4GDlThnekIavHSqXpAwEvmpxLyZWnhhtH2VmNFG4k7cBlJe2Ceb3T/mk3xHSa8YyyBCmpCu4QDCm/uOayQUrXXHxwfn/b6x3Q6EIjxdHl3T9uF6fw9I6IsuGb05cSXW6z36DerDr1kV9qtAhCKLCKgBQLLy1h6zEGqN6a95AfkWpa7BNnaMpJPwkL9kJgDJPrkjD2jTMvtbFZfecs8QodrsgGcgfLAL+1BgNUupRxW61+dE6b8pAWCU3DxzqfSQCnjyApapJzEu+qwio111ZZtTg944KNUujKGK1k6iGq3MXMTIu1UK37asluW7LEKXt4wlkpdedE+++ji+eXJkeoXEzVoX8QPCXb0aLvezhmzpje4ouEZC4b0J6c2C5QwRpJppJaKkFSSCL6FcnlYkqFElORQxeoRW2LYFcq9oH3/Ov3h5O5uOWOo5pqRGBuitdZR45JV8oxDrxgupXhmyX6326fdxBUGi3pr1el46YUPhBtBFXW4Fesjo4urP1Iaj4bKnO4q1iO5+hYrYJnTGcKAhqx+E59ZG85a9S7fNL6X6+Hlp6KPX3jbgjmKJnl24N4BNAwHTPKWyBiveC+eLBYK0eQFb44dMKqwYTv8kvYQ53s1R/frGnO/Y2YFW81Na9DvMiG73aOBt9d0BK9v2RG4WDD40ekOKSuRpNlscT8a37Bdnn0SU062dwscqWNA72RfO97vB4zHqIH2ZWxPI+uSKAxJCRoxaHaYcU3ec8x2LAYFnjpOxrk0hbVT3MFCJWozSx7ypZIhIDhJrfhA57Q+Bz9Npw12SeEWL5EVUdDhT+hSBAoNxEvvIKZSxcQR9OITT+aAUUGnX4SSMp/pldmKQ4zbXSa7W6s2B45b+1EoUO8bCdnIaksuaGHd5ZSkWEW6e2u9gGmkG410nRpTEb3zHtMQR90Oq9JijPfAqiiOluv9/WJ1u5ixzXbDGSmkrSqKGAR1MmmzzMEheoMwL9sOlmUwjWg12IMkNXJSmQqumHJOr9tMWfZ79WY1u3/a+ODk9JRp12ZvQ2xMPRGYHid3NVFan14+Y66SapPy434yJl85A/Zod3e8XAwXnJjHsKptMbInGZdf4/oTTzI9HOFTAajggrgSAHN5I6v0CpfKO1FUtgoVYOZrJQmPhsxbUh0KXAUrv3/6p7VmX6Xck0jpSIwVlkqsDahZ8dXgiyEck3pYYlZEY4hZN6pBVmcsa/Nle9bojRrrzmLHplLGNVdHR/02ByjsaujkDeejvXl79e7tw+0Nw4A91gt2aUFRA9Jwvfv6qzdfv76+Gy0XK/cdNcmJWuNiXxvWazRB0cC4Ix1BLBrDYAZrqNwKZ1HAG8pEucEIO30jDslw0JBpKYbhHbljCQcaCEkebG+tw54ABS94QPVFy1WLyYabVD6se5zP3UcXGZq5icSDLeEiLMoTYxdmkO7gAWEwtsKX0OGeRnITGP+l2UCYSQKdW5a6xsq+JQ337mLGOmq2+bnvhENoWGZgX46SghZpu9XrUYZRAaIdRMi494ybOVj4ByQdj1p7UBsOOwx1lJokkhq0IyyuCFVj79kuzUaV3YL2K8zKStxhKdoBpVsIXWgbJRuzhdGIkGDFx+y3a04xTI5kuU469KSIY+KDnuZXP/p9NHE0W3z6yXeGHNS9acymm/HobjKdcSj4CePgXF+wbJxdPqEYWc7WN1e3D7P7Xn37tDVa12/Y+8IYavQnosx11lKmSSdPcDCNRosJ6TB+3/KkKfMG929aJdWQ5U1fbCL5pkYZ7oAkg6RLIExfHcKK4dEcLoVIMFCETpkLsGkBeKWH4V/kBXMIkVTYmzJdeCF3WrFEJHzAEFnplQ5MSKGEvIv6fFq73zZvuebcG2G368GQmTyUqDmdTG+v3r356vXVW2vC+9trhvSGg/6SU2LuR7Pp9Ob67t3XN/c3HKhGdtTZWooGMgxz2tgf1/c9lpG5qw5SKDvMcASNZmIOsESxLDeVFyhd1ReciD+dEwXHXzj3YKqyE8gxDTFRIWzUVSqEMoeoWoNEEgFtAUaHcMniY1GSXNgqNwB4rA9lNVhwiKCyPp5USEIStcEJxVPB4C6myBEcLT6wU8qFElo8MF/YccLQ0wYbHCLAEkvWX3LOPMUKeQFWhpk73Q6LXVytjnqgjKyX3jJTT51FitgT0mUKnMWwHqDhIR57Z+pC1VSpPatq7u2Eze42cw6+X8gMh+c8NYwhHwsyhmaWtEgp/chs2w7Qah3ozIPpka2ECudwFwPtXdOMwq7ms9GbL39y/dVXVzdXo/H0xcuPQfj26ubu+i17Njm9+eIJnROaoNuH8YfNdgczSvjq9R+1N/fr02VzcN/v3vaHa648h3ASHZiNuTy4BB/jp7gFZRAQzzcs4WgSIgvzEwCmJrO+8kISNMYXr2JIF6wmWzyZzWnWjnNVCqRVrwNmyTdgazpW9+Vb2P1E2g5KKBzMVevgqCMa5oyuRWg1UPKFJlOpUgyrgWwQVwm5qHM8X1+v6hOUsNVltf8l8+YMgbIg5u2r12/evrm7YkTm6v72gSUV7e6k2+Xs+jUqysK1+QOHG1LeO/9+2qgdt+sn7dqwVRu0al2KfpuK0WyDeIpgDid0xEL9oHfBl+QjQMgIIrKY3E/HtyxeS15FEjFCNNUgB7Sxh50JMkXFAok3KjUTDsvQZmR9y45GdhazY98cyTegZB0gSBpf+VR4kzDmQkQnGkc/9E0YfrUWYqLUBknGDDClAgkkQRQSnNHPmQLdxaDbabWWnB3I9oIOp5B73k2bI3xjeIZGZrfXaM+ZonA3IlWS5YzN1lRCVqVSf7pFE0IYDSAO/l2asGccZLRYXU0n75aTW3a5A2iguEzcDKdrylQ5C+ypXDnfqYsSW0SR2bwkQnak9sHHaCPpTASqsgzAE4qYGr67u3r1rsO072jy7s3XjMGM7u8m17f13YLxt9H96OTs7vr24eT6qts/dbLk3Rtqz/rii+7T+dmz9eJksmgsKOFozID18B4YWngLT5Oz2PNJl8pWsiGggg96pKMZ5H95HiFNY/E6QJbCFdgMEpxIszDfhBdj5YIRivxSflKyyqvCrHQuCJOZ2X2KfCQA2QIMMi4kCB2gJmI4bn475k+rhkxeUW2sa9Pd+n55c3vTnmxY6FE/uTjpcr5zs3m72d68vX779u3d7Z2H9r6+Gd1P2cSLrDF8xhoR21Brlh5zs2eNWY7Tfv182DjrNzltvd/Zo4HM4rrHAbJdroy00UmhI8RiRqUHyVX+GI+BNISJIQzOdpjdM7op8W4TDFotRqg7GWqLaTmGVwGlILGNhujARpoHJjDOA2b989JOkdJnivmGfMmK1D3oCS95U2ljoDIiWQbH3hMGHLGmOz4+GZyw5Ak0ETdmxn5JDQOJ7Kdcd2lydpuUTZ1NvbWORTN09xh2oX/MspkOpyDWl3CHpqHzHPQHIZVsQ+3Y4ESxw2SDLGBUx6EoNQWN5UAR2gq3C5ckzVgt7qEeETFtH4ZnzO86gzJrxmY8dLSffZjSDyTvowCTatCpcbzEb6JkqRFRui1btdWwsz9qUamuJm9fr6ZjZmGmnBw0XZAjM1Zw3I0Hw1fdk58cPf3i6OQphf3k/nZ5/ePO9tWGwmW4X3VrC1cLFCWUycFEIkqZlInJU006ymWeQ1ZBWzikI5YD6wVOazjx8bd461WeCgYHKHkfg5l58E349wECvoQK2oiRl1UgJWZUTklJijNqKz4fahRCci61Z5QxpC8M/BE9LCYfQywjctz09AdRde/K+NXtaHw9RkR66/VL5ITNENMR8/LvOLV+/MCsxPj2loWK6KDZxqQUFRdix+zfUbOu+h01eE8HzePuvt/hvOx9m4HBKAiBV2uiOGYcyNEIpNVTiRg+QEKss8CKS531+quFW+DQKPJDFoR2RiXesDEbr7JC+qwFTGWVhR7l6YJzGnKoscmLFxCZ4Y/J5omUhwuiFzTiIia4E2C2oWJMATfdqyc8RZOOBQ928CjjliYsXODajS0DhS2mSdbtGiuK6Hm7jDvauZw2w8wPewE5nwDVso2JElKy2Ed1f2H0Bp27gCfsmqeXi3LsaqwKpd99w8JOBkebO0ZR+x4sw0FctvrID/aQgIE9lQ4HoYsUddEhlMXRCZBMCYdtkVSQ5jBDpgYfubrpNTffOm8cf9QYLfcz9ocsHlaQwEHLFBbMfU7ohSwWrmZ78/D1q+OnT/pHfdrFJ7uro87svLnpodg0tRhuCEmElShhxunvIa7K4G9lxvj4pJ5gj2wrvA7vNCOIelUSDQ6LFL4Z8L1QFrrvYRMyfQODxsBj4Hwqq1oV3ELv2NRLqFAoEOisrATpZLxO6gXlqifDkxHKkO6BggLaI6qVNoQUZykFwgnkeMljG6QP0/V9Y1nvur5/uW612lzbcnNzPZvMl67KYscgm0RZtOnugG5932/Z8jzroniNo6P6cb9+0q0ddTd95srUQPoyHPoloTYeidiTFrytmb0+nJOEwQvpQxujPab4yytrV9cHWazwJPP80orlpUepNJivMsI60LInlMdMQGZZP8mpuXLAV7+AFCoa7JEdgVzeIzBZVsjhGG6RmxEQJgWfcSgP8JZqj4EjisAgHl7HYZhUZTPestuCjZ11rWMTXD1scUyMm9PrzP71ez1a8bCTVTZ7xkcJSkbj7ZoabgCmEgSdi0qJEc1mv+9m/W45fz0f3denm966Pdgx7sPqJsZcqUDRQ8bQrWub9ALgtYcd2oxHvWl7cCepTLNOCuqDf2EhZ3SSkaTMa8B79c3L482TD9nfyAqNOiduUPLabYl205I5Cbq9cdxPc7bu3U0HNfZV7bnr6WS4fXJcO+7WexzODiVRScOrlGNZmJEf2EdOVOb8DdrMFdUmvgTCYND4SXHQMUpNvnqnORwFTceDIRpFGSS9CpIqChFj9qd8wxR4NEk/hLEqWTnWrF5FZw++6U/bEN2yDnAfGRcb2beKQCAxzfwppx6CpzETg5BF1I5COgbSZba+s5+3atv5dvbu9fL+frncc0bodM54p0vAiINT1+np9bgdol077tSOerz1YY9r0Wu9DrXfftDZ9Do7bkhnkRbSEKSbUOsyd9WYkqA/VAEKksxCrL7Ewvml1GMKBYu/aa/ZgqL2oxKho8jOetql8ZgQSo/CDRZrwBZ5RFVNY4zukgoobLA6NQ17cFoEqlnGjCOuJjMoeoQIsOBZERWgKl+CFjMuoS/RqnSfIbU6I0OMXPZ2HPHPLEW34+QE2w8RZA/9R12Yf6U2XLcYvwGc3kLslAGoyykG7NxltpDqjVFVGMLgzW473qzeLqevVvfX24dZZ7ntbFs9VviyolDc6p5FtS9lX7u1amzZVdSv75i6cwsF1EKxhYo5kO0YeyuRsGCD3iaY7isnuZ0drTFAGYND7sWgDKE+tRitU2YwR4IqghWmNzqLbn856NeP+vvhYH9yTK+E+2pqvYqwzPFAffhoODyFpRVn89cv/+85QiDWdC9frJF5cA8pKSEwCGr2lGyLUDgHyHuOgFWhDmF1C/jAUfLYnLa6Ip8pHYOPsJKEZTC+DlXYrIitBS5vtj4kqowaSHIHFVRPMessPZRSPLZeGuiVSnXBapkVgbdxcwTHWNTWHErB/brMNDRqnLXNiOmQLxeUsdVTxdtxBle3U+9y9l8L3eOr+lEkIxASKW3GRpzggfhMHt8o3mPYWsr11UswBkFZ+O92Hwa4HbFDFRl9jPFUe4YmHMFEqnyiE+WCrZwPV4Qosld0chchUWJNZQOYMDLQ2DKbEoPfRAc3CuvDQFis+RXovefgGCnTAxLBHG1lFj/QIp3vZt2iIp1lrcXuEWBZ4ubIPSM0rJvptZZc7OSW2ZgrQTfQpo6HVaGETGKogWgozY/5ZnHFcZ+bh6vdaNpcbdq7emfHHIAaWPU0UwOLHrI9ezutb49sFNrflLVU86ALNYyUyEq6kjADr+B1KChFOD15evXuQbF0EICQJA2tM7sw89pUDuaE2nc6+x6HbPVqg0Ft0Kv1mSUlMTE06phFsjVEAp4GV7MykCnFHgwKe37Crnd5NIY1EmMuYjODE0ZLcTFAuIdbhKq8DsAZ0KAZMAAMGPx4P6DUYkcJeVgdTzY72o9zlRKCJB9B4mg0VZ7EiVz0VhWKEgHlQ/SvrDE0qyqAMInHCMope2wHjIswsb4fdfbjuTPsJ1yB4T3xdTRw0K31u9R1MHdPXddt7uFyp72lv2PG2/gs5bGiDN8j/iSFLMQesRUFIAkmIooVKdTfH1njIRfeNm/fyAKec3YZg9k0G9z57ICNnS8lgdQZDDGByogQi6eB1+KwWtMY3KvwE6cJh2MRTnqEqF4cg//hGo4JkMDpGjgfATDhe8CAmEJfwnd2qz67nJfN9rLRWNInpIajIU4cy8ImVyO16R6yD5mzcWKqBRaiWexKdoLNUQBLByfHR8vlzXp8vx8vGqsdpx0c71lfaKUJdLQ/ZVRUg0SC2d2EHFm/Y17rzGqQjkoQRhFM1oT0ygYcidtU622r3qI55kQ6fZf6WKJHN1ZnXsvk6q2EjJjASW0M6axRRw+p+DEoD44VxRusND7e/98n5NW4AAwx1Rz/VbAQEl2oaMLrEES7TiUg5uIQQRIhwH8yVMKneyA8QAYRQTJh6vX/B02nNRBKV3Z9AAAAAElFTkSuQmCC", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "image/jpeg": "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", - "image/png": "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", + "image/jpeg": "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", + "image/png": "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", "text/plain": [ "" ] diff --git a/notebook/agentchat_lmm_gpt-4v.ipynb b/notebook/agentchat_lmm_gpt-4v.ipynb index 3da51028b63..7c9e3ea125c 100644 --- a/notebook/agentchat_lmm_gpt-4v.ipynb +++ b/notebook/agentchat_lmm_gpt-4v.ipynb @@ -43,17 +43,17 @@ "from typing import Any, Callable, Dict, List, Optional, Tuple, Type, Union\n", "\n", "import matplotlib.pyplot as plt\n", + "import numpy as np\n", "import requests\n", "from PIL import Image\n", "from termcolor import colored\n", - "import numpy as np\n", "\n", "import autogen\n", - "from autogen.code_utils import content_str\n", "from autogen import Agent, AssistantAgent, ConversableAgent, UserProxyAgent\n", "from autogen.agentchat.contrib.capabilities.vision_capability import VisionCapability\n", + "from autogen.agentchat.contrib.img_utils import get_pil_image, pil_to_data_uri\n", "from autogen.agentchat.contrib.multimodal_conversable_agent import MultimodalConversableAgent\n", - "from autogen.agentchat.contrib.img_utils import get_pil_image, pil_to_data_uri" + "from autogen.code_utils import content_str" ] }, { diff --git a/notebook/agentchat_logging.ipynb b/notebook/agentchat_logging.ipynb index c06a8fd743b..7eb4138b4cc 100644 --- a/notebook/agentchat_logging.ipynb +++ b/notebook/agentchat_logging.ipynb @@ -8,6 +8,10 @@ "\n", "AutoGen offers utilities to log data for debugging and performance analysis. This notebook demonstrates how to use them. \n", "\n", + "we log data in different modes:\n", + "- SQlite Database\n", + "- File \n", + "\n", "In general, users can initiate logging by calling `autogen.runtime_logging.start()` and stop logging by calling `autogen.runtime_logging.stop()`" ] }, @@ -38,9 +42,11 @@ ], "source": [ "import json\n", + "\n", + "import pandas as pd\n", + "\n", "import autogen\n", "from autogen import AssistantAgent, UserProxyAgent\n", - "import pandas as pd\n", "\n", "# Setup API key. Add your own API key to config file or environment variable\n", "llm_config = {\n", @@ -285,6 +291,82 @@ " + str(round(session_cost, 4))\n", ")" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Log data in File mode\n", + "\n", + "By default, the log type is set to `sqlite` as shown above, but we introduced a new parameter for the `autogen.runtime_logging.start()`\n", + "\n", + "the `logger_type = \"file\"` will start to log data in the File mode." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Logging session ID: ed493ebf-d78e-49f0-b832-69557276d557\n", + "\u001b[33muser_proxy\u001b[0m (to assistant):\n", + "\n", + "What is the height of the Eiffel Tower? Only respond with the answer and terminate\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33massistant\u001b[0m (to user_proxy):\n", + "\n", + "The height of the Eiffel Tower is 330 meters.\n", + "TERMINATE\n", + "\n", + "--------------------------------------------------------------------------------\n" + ] + } + ], + "source": [ + "\n", + "import pandas as pd\n", + "\n", + "import autogen\n", + "from autogen import AssistantAgent, UserProxyAgent\n", + "\n", + "# Setup API key. Add your own API key to config file or environment variable\n", + "llm_config = {\n", + " \"config_list\": autogen.config_list_from_json(\n", + " env_or_file=\"OAI_CONFIG_LIST\",\n", + " ),\n", + " \"temperature\": 0.9,\n", + "}\n", + "\n", + "# Start logging with logger_type and the filename to log to\n", + "logging_session_id = autogen.runtime_logging.start(logger_type=\"file\", config={\"filename\": \"runtime.log\"})\n", + "print(\"Logging session ID: \" + str(logging_session_id))\n", + "\n", + "# Create an agent workflow and run it\n", + "assistant = AssistantAgent(name=\"assistant\", llm_config=llm_config)\n", + "user_proxy = UserProxyAgent(\n", + " name=\"user_proxy\",\n", + " code_execution_config=False,\n", + " human_input_mode=\"NEVER\",\n", + " is_termination_msg=lambda msg: \"TERMINATE\" in msg[\"content\"],\n", + ")\n", + "\n", + "user_proxy.initiate_chat(\n", + " assistant, message=\"What is the height of the Eiffel Tower? Only respond with the answer and terminate\"\n", + ")\n", + "autogen.runtime_logging.stop()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This should create a `runtime.log` file in your current directory. " + ] } ], "metadata": { @@ -310,7 +392,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.7" + "version": "3.9.13" } }, "nbformat": 4, diff --git a/notebook/agentchat_multi_task_async_chats.ipynb b/notebook/agentchat_multi_task_async_chats.ipynb index 091e5575a64..ad75618a546 100644 --- a/notebook/agentchat_multi_task_async_chats.ipynb +++ b/notebook/agentchat_multi_task_async_chats.ipynb @@ -1737,7 +1737,8 @@ "front_matter": { "description": "Use conversational agents to solve a set of tasks with a sequence of async chats.", "tags": [ - "sequential chat" + "orchestration", + "sequential chats" ] }, "kernelspec": { diff --git a/notebook/agentchat_multi_task_chats.ipynb b/notebook/agentchat_multi_task_chats.ipynb index e640e15ff6d..2c200f52354 100644 --- a/notebook/agentchat_multi_task_chats.ipynb +++ b/notebook/agentchat_multi_task_chats.ipynb @@ -1533,7 +1533,8 @@ "front_matter": { "description": "Use conversational agents to solve a set of tasks with a sequence of chats.", "tags": [ - "sequential chat" + "orchestration", + "sequential chats" ] }, "kernelspec": { diff --git a/notebook/agentchat_nested_chats_chess.ipynb b/notebook/agentchat_nested_chats_chess.ipynb index 52a53de10a9..3e6ba6239d2 100644 --- a/notebook/agentchat_nested_chats_chess.ipynb +++ b/notebook/agentchat_nested_chats_chess.ipynb @@ -62,7 +62,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ @@ -94,14 +94,15 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ + "from typing import List\n", + "\n", "import chess\n", "import chess.svg\n", "from IPython.display import display\n", - "from typing import List\n", "from typing_extensions import Annotated\n", "\n", "# Initialize the board.\n", @@ -149,7 +150,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ @@ -204,7 +205,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 7, "metadata": {}, "outputs": [], "source": [ @@ -251,7 +252,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 8, "metadata": {}, "outputs": [ { @@ -270,7 +271,7 @@ " 'parameters': {'type': 'object', 'properties': {}, 'required': []}}}]" ] }, - "execution_count": 6, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } @@ -305,7 +306,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 9, "metadata": {}, "outputs": [], "source": [ @@ -349,14 +350,14 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "\u001b[33mPlayer White\u001b[0m (to Player Black):\n", + "\u001b[33mPlayer Black\u001b[0m (to Player White):\n", "\n", "Let's play chess! Your move.\n", "\n", @@ -365,25 +366,19 @@ ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", "\u001b[34m\n", "********************************************************************************\u001b[0m\n", - "\u001b[34mStarting a new chat....\n", - "\n", - "Message:\n", - "Let's play chess! Your move.\n", - "\n", - "Carryover: \n", - "\u001b[0m\n", + "\u001b[34mStarting a new chat....\u001b[0m\n", "\u001b[34m\n", "********************************************************************************\u001b[0m\n", - "\u001b[33mBoard Proxy\u001b[0m (to Player Black):\n", + "\u001b[33mBoard Proxy\u001b[0m (to Player White):\n", "\n", "Let's play chess! Your move.\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[31m\n", ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", - "\u001b[33mPlayer Black\u001b[0m (to Board Proxy):\n", + "\u001b[33mPlayer White\u001b[0m (to Board Proxy):\n", "\n", - "\u001b[32m***** Suggested tool Call (call_Jw535t9MZ9DMog6CMk3fleg2): get_legal_moves *****\u001b[0m\n", + "\u001b[32m***** Suggested tool call (call_8aNbVlbAuH1l4f196x6R5Ccv): get_legal_moves *****\u001b[0m\n", "Arguments: \n", "{}\n", "\u001b[32m********************************************************************************\u001b[0m\n", @@ -391,20 +386,20 @@ "--------------------------------------------------------------------------------\n", "\u001b[35m\n", ">>>>>>>> EXECUTING FUNCTION get_legal_moves...\u001b[0m\n", - "\u001b[33mBoard Proxy\u001b[0m (to Player Black):\n", + "\u001b[33mBoard Proxy\u001b[0m (to Player White):\n", "\n", - "\u001b[33mBoard Proxy\u001b[0m (to Player Black):\n", + "\u001b[33mBoard Proxy\u001b[0m (to Player White):\n", "\n", - "\u001b[32m***** Response from calling tool \"call_Jw535t9MZ9DMog6CMk3fleg2\" *****\u001b[0m\n", + "\u001b[32m***** Response from calling tool (call_8aNbVlbAuH1l4f196x6R5Ccv) *****\u001b[0m\n", "Possible moves are: g1h3,g1f3,b1c3,b1a3,h2h3,g2g3,f2f3,e2e3,d2d3,c2c3,b2b3,a2a3,h2h4,g2g4,f2f4,e2e4,d2d4,c2c4,b2b4,a2a4\n", "\u001b[32m**********************************************************************\u001b[0m\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[31m\n", ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", - "\u001b[33mPlayer Black\u001b[0m (to Board Proxy):\n", + "\u001b[33mPlayer White\u001b[0m (to Board Proxy):\n", "\n", - "\u001b[32m***** Suggested tool Call (call_0e8L4c6D0HCBybuqxCD4cgjR): make_move *****\u001b[0m\n", + "\u001b[32m***** Suggested tool call (call_BT0pL4qOUJNt4tH9JhzUWxa0): make_move *****\u001b[0m\n", "Arguments: \n", "{\"move\":\"e2e4\"}\n", "\u001b[32m**************************************************************************\u001b[0m\n", @@ -437,50 +432,44 @@ "name": "stdout", "output_type": "stream", "text": [ - "\u001b[33mBoard Proxy\u001b[0m (to Player Black):\n", + "\u001b[33mBoard Proxy\u001b[0m (to Player White):\n", "\n", - "\u001b[33mBoard Proxy\u001b[0m (to Player Black):\n", + "\u001b[33mBoard Proxy\u001b[0m (to Player White):\n", "\n", - "\u001b[32m***** Response from calling tool \"call_0e8L4c6D0HCBybuqxCD4cgjR\" *****\u001b[0m\n", + "\u001b[32m***** Response from calling tool (call_BT0pL4qOUJNt4tH9JhzUWxa0) *****\u001b[0m\n", "Moved pawn (β™™) from e2 to e4.\n", "\u001b[32m**********************************************************************\u001b[0m\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[31m\n", ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", - "\u001b[33mPlayer Black\u001b[0m (to Board Proxy):\n", + "\u001b[33mPlayer White\u001b[0m (to Board Proxy):\n", "\n", - "I've moved my pawn from e2 to e4. Your move!\n", + "I've moved the pawn from e2 to e4. Your move!\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayer Black\u001b[0m (to Player White):\n", + "\u001b[33mPlayer White\u001b[0m (to Player Black):\n", "\n", - "I've moved my pawn from e2 to e4. Your move!\n", + "I've moved the pawn from e2 to e4. Your move!\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[31m\n", ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", "\u001b[34m\n", "********************************************************************************\u001b[0m\n", - "\u001b[34mStarting a new chat....\n", - "\n", - "Message:\n", - "I've moved my pawn from e2 to e4. Your move!\n", - "\n", - "Carryover: \n", - "\u001b[0m\n", + "\u001b[34mStarting a new chat....\u001b[0m\n", "\u001b[34m\n", "********************************************************************************\u001b[0m\n", - "\u001b[33mBoard Proxy\u001b[0m (to Player White):\n", + "\u001b[33mBoard Proxy\u001b[0m (to Player Black):\n", "\n", - "I've moved my pawn from e2 to e4. Your move!\n", + "I've moved the pawn from e2 to e4. Your move!\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[31m\n", ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", - "\u001b[33mPlayer White\u001b[0m (to Board Proxy):\n", + "\u001b[33mPlayer Black\u001b[0m (to Board Proxy):\n", "\n", - "\u001b[32m***** Suggested tool Call (call_LyBU6E51NuiqROveKaA4EctT): get_legal_moves *****\u001b[0m\n", + "\u001b[32m***** Suggested tool call (call_4kweVDAIgGqvKruWz4PvK01f): get_legal_moves *****\u001b[0m\n", "Arguments: \n", "{}\n", "\u001b[32m********************************************************************************\u001b[0m\n", @@ -488,21 +477,20 @@ "--------------------------------------------------------------------------------\n", "\u001b[35m\n", ">>>>>>>> EXECUTING FUNCTION get_legal_moves...\u001b[0m\n", - "\u001b[33mBoard Proxy\u001b[0m (to Player White):\n", + "\u001b[33mBoard Proxy\u001b[0m (to Player Black):\n", "\n", - "\u001b[33mBoard Proxy\u001b[0m (to Player White):\n", + "\u001b[33mBoard Proxy\u001b[0m (to Player Black):\n", "\n", - "\u001b[32m***** Response from calling tool \"call_LyBU6E51NuiqROveKaA4EctT\" *****\u001b[0m\n", + "\u001b[32m***** Response from calling tool (call_4kweVDAIgGqvKruWz4PvK01f) *****\u001b[0m\n", "Possible moves are: g8h6,g8f6,b8c6,b8a6,h7h6,g7g6,f7f6,e7e6,d7d6,c7c6,b7b6,a7a6,h7h5,g7g5,f7f5,e7e5,d7d5,c7c5,b7b5,a7a5\n", "\u001b[32m**********************************************************************\u001b[0m\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[31m\n", ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", - "\u001b[33mPlayer White\u001b[0m (to Board Proxy):\n", + "\u001b[33mPlayer Black\u001b[0m (to Board Proxy):\n", "\n", - "It's black's turn. I will move my pawn from e7 to e5.\n", - "\u001b[32m***** Suggested tool Call (call_MSLR6pqbwYIaAbfl8qxZbqnc): make_move *****\u001b[0m\n", + "\u001b[32m***** Suggested tool call (call_p3asgsBvtmA7O4aAtgHhYp48): make_move *****\u001b[0m\n", "Arguments: \n", "{\"move\":\"e7e5\"}\n", "\u001b[32m**************************************************************************\u001b[0m\n", @@ -535,23 +523,23 @@ "name": "stdout", "output_type": "stream", "text": [ - "\u001b[33mBoard Proxy\u001b[0m (to Player White):\n", + "\u001b[33mBoard Proxy\u001b[0m (to Player Black):\n", "\n", - "\u001b[33mBoard Proxy\u001b[0m (to Player White):\n", + "\u001b[33mBoard Proxy\u001b[0m (to Player Black):\n", "\n", - "\u001b[32m***** Response from calling tool \"call_MSLR6pqbwYIaAbfl8qxZbqnc\" *****\u001b[0m\n", + "\u001b[32m***** Response from calling tool (call_p3asgsBvtmA7O4aAtgHhYp48) *****\u001b[0m\n", "Moved pawn (β™Ÿ) from e7 to e5.\n", "\u001b[32m**********************************************************************\u001b[0m\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[31m\n", ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", - "\u001b[33mPlayer White\u001b[0m (to Board Proxy):\n", + "\u001b[33mPlayer Black\u001b[0m (to Board Proxy):\n", "\n", "I've moved my pawn from e7 to e5. Your move!\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayer White\u001b[0m (to Player Black):\n", + "\u001b[33mPlayer Black\u001b[0m (to Player White):\n", "\n", "I've moved my pawn from e7 to e5. Your move!\n", "\n", @@ -560,79 +548,64 @@ ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", "\u001b[34m\n", "********************************************************************************\u001b[0m\n", - "\u001b[34mStarting a new chat....\n", - "\n", - "Message:\n", - "I've moved my pawn from e7 to e5. Your move!\n", - "\n", - "Carryover: \n", - "\u001b[0m\n", + "\u001b[34mStarting a new chat....\u001b[0m\n", "\u001b[34m\n", "********************************************************************************\u001b[0m\n", - "\u001b[33mBoard Proxy\u001b[0m (to Player Black):\n", + "\u001b[33mBoard Proxy\u001b[0m (to Player White):\n", "\n", "I've moved my pawn from e7 to e5. Your move!\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[31m\n", ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", - "\u001b[33mPlayer Black\u001b[0m (to Board Proxy):\n", + "\u001b[33mPlayer White\u001b[0m (to Board Proxy):\n", "\n", - "\u001b[32m***** Suggested tool Call (call_gaqEpvOSEaDoh1wxvrDpwVCe): make_move *****\u001b[0m\n", + "\u001b[32m***** Suggested tool call (call_9ynncokEz6NnIAy4RWLoUSb6): get_legal_moves *****\u001b[0m\n", "Arguments: \n", - "{\"move\":\"e2e4\"}\n", - "\u001b[32m**************************************************************************\u001b[0m\n", + "{}\n", + "\u001b[32m********************************************************************************\u001b[0m\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[35m\n", - ">>>>>>>> EXECUTING FUNCTION make_move...\u001b[0m\n", - "\u001b[33mBoard Proxy\u001b[0m (to Player Black):\n", + ">>>>>>>> EXECUTING FUNCTION get_legal_moves...\u001b[0m\n", + "\u001b[33mBoard Proxy\u001b[0m (to Player White):\n", "\n", - "\u001b[33mBoard Proxy\u001b[0m (to Player Black):\n", + "\u001b[33mBoard Proxy\u001b[0m (to Player White):\n", "\n", - "\u001b[32m***** Response from calling tool \"call_gaqEpvOSEaDoh1wxvrDpwVCe\" *****\u001b[0m\n", - "Error: illegal uci: 'e2e4' in rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2\n", + "\u001b[32m***** Response from calling tool (call_9ynncokEz6NnIAy4RWLoUSb6) *****\u001b[0m\n", + "Possible moves are: g1h3,g1f3,g1e2,f1a6,f1b5,f1c4,f1d3,f1e2,e1e2,d1h5,d1g4,d1f3,d1e2,b1c3,b1a3,h2h3,g2g3,f2f3,d2d3,c2c3,b2b3,a2a3,h2h4,g2g4,f2f4,d2d4,c2c4,b2b4,a2a4\n", "\u001b[32m**********************************************************************\u001b[0m\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[31m\n", ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", - "\u001b[33mPlayer Black\u001b[0m (to Board Proxy):\n", + "\u001b[33mPlayer White\u001b[0m (to Board Proxy):\n", "\n", - "\u001b[32m***** Suggested tool Call (call_BJWUGbFeqnYUwY8x6yEq6Aug): get_legal_moves *****\u001b[0m\n", + "\u001b[32m***** Suggested tool call (call_ohlmvsDY5fFi9JaryU2y9IhS): make_move *****\u001b[0m\n", "Arguments: \n", - "{}\n", - "\u001b[32m********************************************************************************\u001b[0m\n", + "{\"move\":\"e2e4\"}\n", + "\u001b[32m**************************************************************************\u001b[0m\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[35m\n", - ">>>>>>>> EXECUTING FUNCTION get_legal_moves...\u001b[0m\n", - "\u001b[33mBoard Proxy\u001b[0m (to Player Black):\n", + ">>>>>>>> EXECUTING FUNCTION make_move...\u001b[0m\n", + "\u001b[33mBoard Proxy\u001b[0m (to Player White):\n", "\n", - "\u001b[33mBoard Proxy\u001b[0m (to Player Black):\n", + "\u001b[33mBoard Proxy\u001b[0m (to Player White):\n", "\n", - "\u001b[32m***** Response from calling tool \"call_BJWUGbFeqnYUwY8x6yEq6Aug\" *****\u001b[0m\n", - "Possible moves are: g1h3,g1f3,g1e2,f1a6,f1b5,f1c4,f1d3,f1e2,e1e2,d1h5,d1g4,d1f3,d1e2,b1c3,b1a3,h2h3,g2g3,f2f3,d2d3,c2c3,b2b3,a2a3,h2h4,g2g4,f2f4,d2d4,c2c4,b2b4,a2a4\n", + "\u001b[32m***** Response from calling tool (call_ohlmvsDY5fFi9JaryU2y9IhS) *****\u001b[0m\n", + "Error: illegal uci: 'e2e4' in rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2\n", "\u001b[32m**********************************************************************\u001b[0m\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[31m\n", ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", - "\u001b[33mPlayer Black\u001b[0m (to Board Proxy):\n", - "\n", - "I'll move my pawn from d2 to d4. Your turn!\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mBoard Proxy\u001b[0m (to Player Black):\n", + "\u001b[33mPlayer White\u001b[0m (to Board Proxy):\n", "\n", - "Please make a move.\n", + "It looks like there was an error with my intended move, which seems to be a misunderstanding since the move I tried to make (e2 to e4) is not legal given the board's current state. Since I mistakenly interpreted the board's initial setup and your move, let's proceed correctly based on the actual state of the game.\n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[31m\n", - ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", - "\u001b[33mPlayer Black\u001b[0m (to Board Proxy):\n", - "\n", - "\u001b[32m***** Suggested tool Call (call_tfSdfPTJgq3JeIOtT5NO2SJn): make_move *****\u001b[0m\n", + "Based on the available moves, I will now choose a different move to make. Let's go with d2 to d4.\n", + "\u001b[32m***** Suggested tool call (call_zNB1QN26j5T0cggskYabBJRs): make_move *****\u001b[0m\n", "Arguments: \n", "{\"move\":\"d2d4\"}\n", "\u001b[32m**************************************************************************\u001b[0m\n", @@ -665,23 +638,23 @@ "name": "stdout", "output_type": "stream", "text": [ - "\u001b[33mBoard Proxy\u001b[0m (to Player Black):\n", + "\u001b[33mBoard Proxy\u001b[0m (to Player White):\n", "\n", - "\u001b[33mBoard Proxy\u001b[0m (to Player Black):\n", + "\u001b[33mBoard Proxy\u001b[0m (to Player White):\n", "\n", - "\u001b[32m***** Response from calling tool \"call_tfSdfPTJgq3JeIOtT5NO2SJn\" *****\u001b[0m\n", + "\u001b[32m***** Response from calling tool (call_zNB1QN26j5T0cggskYabBJRs) *****\u001b[0m\n", "Moved pawn (β™™) from d2 to d4.\n", "\u001b[32m**********************************************************************\u001b[0m\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[31m\n", ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", - "\u001b[33mPlayer Black\u001b[0m (to Board Proxy):\n", + "\u001b[33mPlayer White\u001b[0m (to Board Proxy):\n", "\n", "I've moved my pawn from d2 to d4. Your move!\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayer Black\u001b[0m (to Player White):\n", + "\u001b[33mPlayer White\u001b[0m (to Player Black):\n", "\n", "I've moved my pawn from d2 to d4. Your move!\n", "\n", @@ -690,25 +663,19 @@ ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", "\u001b[34m\n", "********************************************************************************\u001b[0m\n", - "\u001b[34mStarting a new chat....\n", - "\n", - "Message:\n", - "I've moved my pawn from d2 to d4. Your move!\n", - "\n", - "Carryover: \n", - "\u001b[0m\n", + "\u001b[34mStarting a new chat....\u001b[0m\n", "\u001b[34m\n", "********************************************************************************\u001b[0m\n", - "\u001b[33mBoard Proxy\u001b[0m (to Player White):\n", + "\u001b[33mBoard Proxy\u001b[0m (to Player Black):\n", "\n", "I've moved my pawn from d2 to d4. Your move!\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[31m\n", ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", - "\u001b[33mPlayer White\u001b[0m (to Board Proxy):\n", + "\u001b[33mPlayer Black\u001b[0m (to Board Proxy):\n", "\n", - "\u001b[32m***** Suggested tool Call (call_tLmkAFcQLMP7LHXKSAcUgPpA): get_legal_moves *****\u001b[0m\n", + "\u001b[32m***** Suggested tool call (call_NXjSt2zzC6e342henmdTTV6U): get_legal_moves *****\u001b[0m\n", "Arguments: \n", "{}\n", "\u001b[32m********************************************************************************\u001b[0m\n", @@ -716,22 +683,22 @@ "--------------------------------------------------------------------------------\n", "\u001b[35m\n", ">>>>>>>> EXECUTING FUNCTION get_legal_moves...\u001b[0m\n", - "\u001b[33mBoard Proxy\u001b[0m (to Player White):\n", + "\u001b[33mBoard Proxy\u001b[0m (to Player Black):\n", "\n", - "\u001b[33mBoard Proxy\u001b[0m (to Player White):\n", + "\u001b[33mBoard Proxy\u001b[0m (to Player Black):\n", "\n", - "\u001b[32m***** Response from calling tool \"call_tLmkAFcQLMP7LHXKSAcUgPpA\" *****\u001b[0m\n", + "\u001b[32m***** Response from calling tool (call_NXjSt2zzC6e342henmdTTV6U) *****\u001b[0m\n", "Possible moves are: g8e7,g8h6,g8f6,f8e7,f8d6,f8c5,f8b4,f8a3,e8e7,d8e7,d8f6,d8g5,d8h4,b8c6,b8a6,e5d4,h7h6,g7g6,f7f6,d7d6,c7c6,b7b6,a7a6,h7h5,g7g5,f7f5,d7d5,c7c5,b7b5,a7a5\n", "\u001b[32m**********************************************************************\u001b[0m\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[31m\n", ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", - "\u001b[33mPlayer White\u001b[0m (to Board Proxy):\n", + "\u001b[33mPlayer Black\u001b[0m (to Board Proxy):\n", "\n", - "\u001b[32m***** Suggested tool Call (call_z6PVz3XkfDsfEbsBrMODJm7A): make_move *****\u001b[0m\n", + "\u001b[32m***** Suggested tool call (call_yBiZaYTC96uQGwJWYdg1qra0): make_move *****\u001b[0m\n", "Arguments: \n", - "{\"move\":\"e5d4\"}\n", + "{\"move\":\"d7d5\"}\n", "\u001b[32m**************************************************************************\u001b[0m\n", "\n", "--------------------------------------------------------------------------------\n", @@ -743,16 +710,16 @@ "data": { "image/svg+xml": [ "
r n b q k b n r\n",
-       "p p p p . p p p\n",
+       "p p p . . p p p\n",
        ". . . . . . . .\n",
-       ". . . . . . . .\n",
-       ". . . p P . . .\n",
+       ". . . p p . . .\n",
+       ". . . P P . . .\n",
        ". . . . . . . .\n",
        "P P P . . P P P\n",
-       "R N B Q K B N R
" + "R N B Q K B N R" ], "text/plain": [ - "'
r n b q k b n r\\np p p p . p p p\\n. . . . . . . .\\n. . . . . . . .\\n. . . p P . . .\\n. . . . . . . .\\nP P P . . P P P\\nR N B Q K B N R
'" + "'
r n b q k b n r\\np p p . . p p p\\n. . . . . . . .\\n. . . p p . . .\\n. . . P P . . .\\n. . . . . . . .\\nP P P . . P P P\\nR N B Q K B N R
'" ] }, "metadata": {}, @@ -762,50 +729,44 @@ "name": "stdout", "output_type": "stream", "text": [ - "\u001b[33mBoard Proxy\u001b[0m (to Player White):\n", + "\u001b[33mBoard Proxy\u001b[0m (to Player Black):\n", "\n", - "\u001b[33mBoard Proxy\u001b[0m (to Player White):\n", + "\u001b[33mBoard Proxy\u001b[0m (to Player Black):\n", "\n", - "\u001b[32m***** Response from calling tool \"call_z6PVz3XkfDsfEbsBrMODJm7A\" *****\u001b[0m\n", - "Moved pawn (β™Ÿ) from e5 to d4.\n", + "\u001b[32m***** Response from calling tool (call_yBiZaYTC96uQGwJWYdg1qra0) *****\u001b[0m\n", + "Moved pawn (β™Ÿ) from d7 to d5.\n", "\u001b[32m**********************************************************************\u001b[0m\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[31m\n", ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", - "\u001b[33mPlayer White\u001b[0m (to Board Proxy):\n", + "\u001b[33mPlayer Black\u001b[0m (to Board Proxy):\n", "\n", - "I've captured your pawn by moving my pawn from e5 to d4. Your move!\n", + "I've moved my pawn from d7 to d5. Your move!\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayer White\u001b[0m (to Player Black):\n", + "\u001b[33mPlayer Black\u001b[0m (to Player White):\n", "\n", - "I've captured your pawn by moving my pawn from e5 to d4. Your move!\n", + "I've moved my pawn from d7 to d5. Your move!\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[31m\n", ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", "\u001b[34m\n", "********************************************************************************\u001b[0m\n", - "\u001b[34mStarting a new chat....\n", - "\n", - "Message:\n", - "I've captured your pawn by moving my pawn from e5 to d4. Your move!\n", - "\n", - "Carryover: \n", - "\u001b[0m\n", + "\u001b[34mStarting a new chat....\u001b[0m\n", "\u001b[34m\n", "********************************************************************************\u001b[0m\n", - "\u001b[33mBoard Proxy\u001b[0m (to Player Black):\n", + "\u001b[33mBoard Proxy\u001b[0m (to Player White):\n", "\n", - "I've captured your pawn by moving my pawn from e5 to d4. Your move!\n", + "I've moved my pawn from d7 to d5. Your move!\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[31m\n", ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", - "\u001b[33mPlayer Black\u001b[0m (to Board Proxy):\n", + "\u001b[33mPlayer White\u001b[0m (to Board Proxy):\n", "\n", - "\u001b[32m***** Suggested tool Call (call_CedmvIwaBWk23QxMZunlaOYt): get_legal_moves *****\u001b[0m\n", + "\u001b[32m***** Suggested tool call (call_xl3cbAtWFeOX05gaOdGLnZQk): get_legal_moves *****\u001b[0m\n", "Arguments: \n", "{}\n", "\u001b[32m********************************************************************************\u001b[0m\n", @@ -813,22 +774,22 @@ "--------------------------------------------------------------------------------\n", "\u001b[35m\n", ">>>>>>>> EXECUTING FUNCTION get_legal_moves...\u001b[0m\n", - "\u001b[33mBoard Proxy\u001b[0m (to Player Black):\n", + "\u001b[33mBoard Proxy\u001b[0m (to Player White):\n", "\n", - "\u001b[33mBoard Proxy\u001b[0m (to Player Black):\n", + "\u001b[33mBoard Proxy\u001b[0m (to Player White):\n", "\n", - "\u001b[32m***** Response from calling tool \"call_CedmvIwaBWk23QxMZunlaOYt\" *****\u001b[0m\n", - "Possible moves are: g1h3,g1f3,g1e2,f1a6,f1b5,f1c4,f1d3,f1e2,e1e2,e1d2,d1h5,d1g4,d1d4,d1f3,d1d3,d1e2,d1d2,c1h6,c1g5,c1f4,c1e3,c1d2,b1c3,b1a3,b1d2,e4e5,h2h3,g2g3,f2f3,c2c3,b2b3,a2a3,h2h4,g2g4,f2f4,c2c4,b2b4,a2a4\n", + "\u001b[32m***** Response from calling tool (call_xl3cbAtWFeOX05gaOdGLnZQk) *****\u001b[0m\n", + "Possible moves are: g1h3,g1f3,g1e2,f1a6,f1b5,f1c4,f1d3,f1e2,e1e2,e1d2,d1h5,d1g4,d1f3,d1d3,d1e2,d1d2,c1h6,c1g5,c1f4,c1e3,c1d2,b1c3,b1a3,b1d2,e4d5,d4e5,h2h3,g2g3,f2f3,c2c3,b2b3,a2a3,h2h4,g2g4,f2f4,c2c4,b2b4,a2a4\n", "\u001b[32m**********************************************************************\u001b[0m\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[31m\n", ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", - "\u001b[33mPlayer Black\u001b[0m (to Board Proxy):\n", + "\u001b[33mPlayer White\u001b[0m (to Board Proxy):\n", "\n", - "\u001b[32m***** Suggested tool Call (call_gaqEpvOSEaDoh1wxvrDpwVCe): make_move *****\u001b[0m\n", + "\u001b[32m***** Suggested tool call (call_qwjiKCij3YKIdaebFwtSeU4C): make_move *****\u001b[0m\n", "Arguments: \n", - "{\"move\":\"d1d4\"}\n", + "{\"move\":\"e4d5\"}\n", "\u001b[32m**************************************************************************\u001b[0m\n", "\n", "--------------------------------------------------------------------------------\n", @@ -840,16 +801,16 @@ "data": { "image/svg+xml": [ "
r n b q k b n r\n",
-       "p p p p . p p p\n",
-       ". . . . . . . .\n",
+       "p p p . . p p p\n",
        ". . . . . . . .\n",
-       ". . . Q P . . .\n",
+       ". . . P p . . .\n",
+       ". . . P . . . .\n",
        ". . . . . . . .\n",
        "P P P . . P P P\n",
-       "R N B . K B N R
" + "R N B Q K B N R" ], "text/plain": [ - "'
r n b q k b n r\\np p p p . p p p\\n. . . . . . . .\\n. . . . . . . .\\n. . . Q P . . .\\n. . . . . . . .\\nP P P . . P P P\\nR N B . K B N R
'" + "'
r n b q k b n r\\np p p . . p p p\\n. . . . . . . .\\n. . . P p . . .\\n. . . P . . . .\\n. . . . . . . .\\nP P P . . P P P\\nR N B Q K B N R
'" ] }, "metadata": {}, @@ -859,50 +820,44 @@ "name": "stdout", "output_type": "stream", "text": [ - "\u001b[33mBoard Proxy\u001b[0m (to Player Black):\n", + "\u001b[33mBoard Proxy\u001b[0m (to Player White):\n", "\n", - "\u001b[33mBoard Proxy\u001b[0m (to Player Black):\n", + "\u001b[33mBoard Proxy\u001b[0m (to Player White):\n", "\n", - "\u001b[32m***** Response from calling tool \"call_gaqEpvOSEaDoh1wxvrDpwVCe\" *****\u001b[0m\n", - "Moved queen (β™•) from d1 to d4.\n", + "\u001b[32m***** Response from calling tool (call_qwjiKCij3YKIdaebFwtSeU4C) *****\u001b[0m\n", + "Moved pawn (β™™) from e4 to d5.\n", "\u001b[32m**********************************************************************\u001b[0m\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[31m\n", ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", - "\u001b[33mPlayer Black\u001b[0m (to Board Proxy):\n", + "\u001b[33mPlayer White\u001b[0m (to Board Proxy):\n", "\n", - "I've moved my queen from d1 to d4, capturing your pawn. Your move!\n", + "I've moved my pawn from e4 to d5. Your move!\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayer Black\u001b[0m (to Player White):\n", + "\u001b[33mPlayer White\u001b[0m (to Player Black):\n", "\n", - "I've moved my queen from d1 to d4, capturing your pawn. Your move!\n", + "I've moved my pawn from e4 to d5. Your move!\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[31m\n", ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", "\u001b[34m\n", "********************************************************************************\u001b[0m\n", - "\u001b[34mStarting a new chat....\n", - "\n", - "Message:\n", - "I've moved my queen from d1 to d4, capturing your pawn. Your move!\n", - "\n", - "Carryover: \n", - "\u001b[0m\n", + "\u001b[34mStarting a new chat....\u001b[0m\n", "\u001b[34m\n", "********************************************************************************\u001b[0m\n", - "\u001b[33mBoard Proxy\u001b[0m (to Player White):\n", + "\u001b[33mBoard Proxy\u001b[0m (to Player Black):\n", "\n", - "I've moved my queen from d1 to d4, capturing your pawn. Your move!\n", + "I've moved my pawn from e4 to d5. Your move!\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[31m\n", ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", - "\u001b[33mPlayer White\u001b[0m (to Board Proxy):\n", + "\u001b[33mPlayer Black\u001b[0m (to Board Proxy):\n", "\n", - "\u001b[32m***** Suggested tool Call (call_JSsR85jDNRO58KCJFmeUU66J): get_legal_moves *****\u001b[0m\n", + "\u001b[32m***** Suggested tool call (call_zNB1QN26j5T0cggskYabBJRs): get_legal_moves *****\u001b[0m\n", "Arguments: \n", "{}\n", "\u001b[32m********************************************************************************\u001b[0m\n", @@ -910,23 +865,22 @@ "--------------------------------------------------------------------------------\n", "\u001b[35m\n", ">>>>>>>> EXECUTING FUNCTION get_legal_moves...\u001b[0m\n", - "\u001b[33mBoard Proxy\u001b[0m (to Player White):\n", + "\u001b[33mBoard Proxy\u001b[0m (to Player Black):\n", "\n", - "\u001b[33mBoard Proxy\u001b[0m (to Player White):\n", + "\u001b[33mBoard Proxy\u001b[0m (to Player Black):\n", "\n", - "\u001b[32m***** Response from calling tool \"call_JSsR85jDNRO58KCJFmeUU66J\" *****\u001b[0m\n", - "Possible moves are: g8e7,g8h6,g8f6,f8e7,f8d6,f8c5,f8b4,f8a3,e8e7,d8e7,d8f6,d8g5,d8h4,b8c6,b8a6,h7h6,g7g6,f7f6,d7d6,c7c6,b7b6,a7a6,h7h5,g7g5,f7f5,d7d5,c7c5,b7b5,a7a5\n", + "\u001b[32m***** Response from calling tool (call_zNB1QN26j5T0cggskYabBJRs) *****\u001b[0m\n", + "Possible moves are: g8e7,g8h6,g8f6,f8e7,f8d6,f8c5,f8b4,f8a3,e8e7,e8d7,d8e7,d8d7,d8f6,d8d6,d8g5,d8d5,d8h4,c8d7,c8e6,c8f5,c8g4,c8h3,b8d7,b8c6,b8a6,e5d4,h7h6,g7g6,f7f6,c7c6,b7b6,a7a6,e5e4,h7h5,g7g5,f7f5,c7c5,b7b5,a7a5\n", "\u001b[32m**********************************************************************\u001b[0m\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[31m\n", ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", - "\u001b[33mPlayer White\u001b[0m (to Board Proxy):\n", + "\u001b[33mPlayer Black\u001b[0m (to Board Proxy):\n", "\n", - "It's your turn, and you have a wide range of moves available. To keep the game interesting, I will move my knight from b8 to c6. Let's see how this plays out!\n", - "\u001b[32m***** Suggested tool Call (call_QH2T8CK9SUhUiwyPW5kbabaj): make_move *****\u001b[0m\n", + "\u001b[32m***** Suggested tool call (call_bBbRQByx2cqL1BrHi79qzuUj): make_move *****\u001b[0m\n", "Arguments: \n", - "{\"move\":\"b8c6\"}\n", + "{\"move\":\"d8d5\"}\n", "\u001b[32m**************************************************************************\u001b[0m\n", "\n", "--------------------------------------------------------------------------------\n", @@ -937,17 +891,17 @@ { "data": { "image/svg+xml": [ - "
r . b q k b n r\n",
-       "p p p p . p p p\n",
-       ". . n . . . . .\n",
+       "
r n b . k b n r\n",
+       "p p p . . p p p\n",
        ". . . . . . . .\n",
-       ". . . Q P . . .\n",
+       ". . . q p . . .\n",
+       ". . . P . . . .\n",
        ". . . . . . . .\n",
        "P P P . . P P P\n",
-       "R N B . K B N R
" + "R N B Q K B N R
" ], "text/plain": [ - "'
r . b q k b n r\\np p p p . p p p\\n. . n . . . . .\\n. . . . . . . .\\n. . . Q P . . .\\n. . . . . . . .\\nP P P . . P P P\\nR N B . K B N R
'" + "'
r n b . k b n r\\np p p . . p p p\\n. . . . . . . .\\n. . . q p . . .\\n. . . P . . . .\\n. . . . . . . .\\nP P P . . P P P\\nR N B Q K B N R
'" ] }, "metadata": {}, @@ -957,106 +911,93 @@ "name": "stdout", "output_type": "stream", "text": [ - "\u001b[33mBoard Proxy\u001b[0m (to Player White):\n", + "\u001b[33mBoard Proxy\u001b[0m (to Player Black):\n", "\n", - "\u001b[33mBoard Proxy\u001b[0m (to Player White):\n", + "\u001b[33mBoard Proxy\u001b[0m (to Player Black):\n", "\n", - "\u001b[32m***** Response from calling tool \"call_QH2T8CK9SUhUiwyPW5kbabaj\" *****\u001b[0m\n", - "Moved knight (β™ž) from b8 to c6.\n", + "\u001b[32m***** Response from calling tool (call_bBbRQByx2cqL1BrHi79qzuUj) *****\u001b[0m\n", + "Moved queen (β™›) from d8 to d5.\n", "\u001b[32m**********************************************************************\u001b[0m\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[31m\n", ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", - "\u001b[33mPlayer White\u001b[0m (to Board Proxy):\n", + "\u001b[33mPlayer Black\u001b[0m (to Board Proxy):\n", "\n", - "I've moved my knight from b8 to c6. Your move!\n", + "I've moved my queen from d8 to d5. Your move!\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayer White\u001b[0m (to Player Black):\n", + "\u001b[33mPlayer Black\u001b[0m (to Player White):\n", "\n", - "I've moved my knight from b8 to c6. Your move!\n", + "I've moved my queen from d8 to d5. Your move!\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[31m\n", ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", "\u001b[34m\n", "********************************************************************************\u001b[0m\n", - "\u001b[34mStarting a new chat....\n", - "\n", - "Message:\n", - "I've moved my knight from b8 to c6. Your move!\n", - "\n", - "Carryover: \n", - "\u001b[0m\n", + "\u001b[34mStarting a new chat....\u001b[0m\n", "\u001b[34m\n", "********************************************************************************\u001b[0m\n", - "\u001b[33mBoard Proxy\u001b[0m (to Player Black):\n", + "\u001b[33mBoard Proxy\u001b[0m (to Player White):\n", "\n", - "I've moved my knight from b8 to c6. Your move!\n", + "I've moved my queen from d8 to d5. Your move!\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[31m\n", ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", - "\u001b[33mPlayer Black\u001b[0m (to Board Proxy):\n", + "\u001b[33mPlayer White\u001b[0m (to Board Proxy):\n", "\n", - "\u001b[32m***** Suggested tool Call (call_WwJj6w6Wwi1gOklMDbUhUJuG): make_move *****\u001b[0m\n", + "\u001b[32m***** Suggested tool call (call_p3asgsBvtmA7O4aAtgHhYp48): get_legal_moves *****\u001b[0m\n", "Arguments: \n", - "{\"move\":\"b8c6\"}\n", - "\u001b[32m**************************************************************************\u001b[0m\n", + "{}\n", + "\u001b[32m********************************************************************************\u001b[0m\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[35m\n", - ">>>>>>>> EXECUTING FUNCTION make_move...\u001b[0m\n", - "\u001b[33mBoard Proxy\u001b[0m (to Player Black):\n", + ">>>>>>>> EXECUTING FUNCTION get_legal_moves...\u001b[0m\n", + "\u001b[33mBoard Proxy\u001b[0m (to Player White):\n", "\n", - "\u001b[33mBoard Proxy\u001b[0m (to Player Black):\n", + "\u001b[33mBoard Proxy\u001b[0m (to Player White):\n", "\n", - "\u001b[32m***** Response from calling tool \"call_WwJj6w6Wwi1gOklMDbUhUJuG\" *****\u001b[0m\n", - "Error: illegal uci: 'b8c6' in r1bqkbnr/pppp1ppp/2n5/8/3QP3/8/PPP2PPP/RNB1KBNR w KQkq - 1 4\n", + "\u001b[32m***** Response from calling tool (call_p3asgsBvtmA7O4aAtgHhYp48) *****\u001b[0m\n", + "Possible moves are: g1h3,g1f3,g1e2,f1a6,f1b5,f1c4,f1d3,f1e2,e1e2,e1d2,d1h5,d1g4,d1f3,d1d3,d1e2,d1d2,c1h6,c1g5,c1f4,c1e3,c1d2,b1c3,b1a3,b1d2,d4e5,h2h3,g2g3,f2f3,c2c3,b2b3,a2a3,h2h4,g2g4,f2f4,c2c4,b2b4,a2a4\n", "\u001b[32m**********************************************************************\u001b[0m\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[31m\n", ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", - "\u001b[33mPlayer Black\u001b[0m (to Board Proxy):\n", + "\u001b[33mPlayer White\u001b[0m (to Board Proxy):\n", "\n", - "\u001b[32m***** Suggested tool Call (call_fx5RRC2AGI1XoARH2EjBns8G): get_legal_moves *****\u001b[0m\n", + "\u001b[32m***** Suggested tool call (call_NXjSt2zzC6e342henmdTTV6U): make_move *****\u001b[0m\n", "Arguments: \n", - "{}\n", - "\u001b[32m********************************************************************************\u001b[0m\n", + "{\"move\":\"d2d4\"}\n", + "\u001b[32m**************************************************************************\u001b[0m\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[35m\n", - ">>>>>>>> EXECUTING FUNCTION get_legal_moves...\u001b[0m\n", - "\u001b[33mBoard Proxy\u001b[0m (to Player Black):\n", + ">>>>>>>> EXECUTING FUNCTION make_move...\u001b[0m\n", + "\u001b[33mBoard Proxy\u001b[0m (to Player White):\n", "\n", - "\u001b[33mBoard Proxy\u001b[0m (to Player Black):\n", + "\u001b[33mBoard Proxy\u001b[0m (to Player White):\n", "\n", - "\u001b[32m***** Response from calling tool \"call_fx5RRC2AGI1XoARH2EjBns8G\" *****\u001b[0m\n", - "Possible moves are: d4g7,d4d7,d4a7,d4f6,d4d6,d4b6,d4e5,d4d5,d4c5,d4c4,d4b4,d4a4,d4e3,d4d3,d4c3,d4d2,d4d1,g1h3,g1f3,g1e2,f1a6,f1b5,f1c4,f1d3,f1e2,e1e2,e1d2,e1d1,c1h6,c1g5,c1f4,c1e3,c1d2,b1c3,b1a3,b1d2,e4e5,h2h3,g2g3,f2f3,c2c3,b2b3,a2a3,h2h4,g2g4,f2f4,c2c4,b2b4,a2a4\n", + "\u001b[32m***** Response from calling tool (call_NXjSt2zzC6e342henmdTTV6U) *****\u001b[0m\n", + "Error: illegal uci: 'd2d4' in rnb1kbnr/ppp2ppp/8/3qp3/3P4/8/PPP2PPP/RNBQKBNR w KQkq - 0 4\n", "\u001b[32m**********************************************************************\u001b[0m\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[31m\n", ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", - "\u001b[33mPlayer Black\u001b[0m (to Board Proxy):\n", + "\u001b[33mPlayer White\u001b[0m (to Board Proxy):\n", "\n", - "It seems there was a misunderstanding with your move as \"b8c6\" is not a valid move right now. Please ensure your move is from the current board position and legal. If you'd like to suggest another move or need assistance, please let me know!\n", + "It appears I made an error in processing moves, as the move I attempted is invalid based on the current position. Since my previous attempt to move a pawn from d2 to d4 is not legal in the current board setup, let me choose a new move.\n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mBoard Proxy\u001b[0m (to Player Black):\n", + "Given the legal moves provided and the error, I will select another move. Let's proceed with a different strategy:\n", "\n", - "Please make a move.\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[31m\n", - ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", - "\u001b[33mPlayer Black\u001b[0m (to Board Proxy):\n", - "\n", - "\u001b[32m***** Suggested tool Call (call_e4oYuwkcrLmooN9AKcrgBucB): make_move *****\u001b[0m\n", + "I will move my knight from g1 to f3.\n", + "\u001b[32m***** Suggested tool call (call_hj4SR70DRGdP85yRvzFNKx62): make_move *****\u001b[0m\n", "Arguments: \n", - "{\"move\":\"d4d5\"}\n", + "{\"move\":\"g1f3\"}\n", "\u001b[32m**************************************************************************\u001b[0m\n", "\n", "--------------------------------------------------------------------------------\n", @@ -1067,17 +1008,17 @@ { "data": { "image/svg+xml": [ - "
r . b q k b n r\n",
-       "p p p p . p p p\n",
-       ". . n . . . . .\n",
-       ". . . Q . . . .\n",
-       ". . . . P . . .\n",
+       "
r n b . k b n r\n",
+       "p p p . . p p p\n",
        ". . . . . . . .\n",
+       ". . . q p . . .\n",
+       ". . . P . . . .\n",
+       ". . . . . N . .\n",
        "P P P . . P P P\n",
-       "R N B . K B N R
" + "R N B Q K B . R
" ], "text/plain": [ - "'
r . b q k b n r\\np p p p . p p p\\n. . n . . . . .\\n. . . Q . . . .\\n. . . . P . . .\\n. . . . . . . .\\nP P P . . P P P\\nR N B . K B N R
'" + "'
r n b . k b n r\\np p p . . p p p\\n. . . . . . . .\\n. . . q p . . .\\n. . . P . . . .\\n. . . . . N . .\\nP P P . . P P P\\nR N B Q K B . R
'" ] }, "metadata": {}, @@ -1087,25 +1028,25 @@ "name": "stdout", "output_type": "stream", "text": [ - "\u001b[33mBoard Proxy\u001b[0m (to Player Black):\n", + "\u001b[33mBoard Proxy\u001b[0m (to Player White):\n", "\n", - "\u001b[33mBoard Proxy\u001b[0m (to Player Black):\n", + "\u001b[33mBoard Proxy\u001b[0m (to Player White):\n", "\n", - "\u001b[32m***** Response from calling tool \"call_e4oYuwkcrLmooN9AKcrgBucB\" *****\u001b[0m\n", - "Moved queen (β™•) from d4 to d5.\n", + "\u001b[32m***** Response from calling tool (call_hj4SR70DRGdP85yRvzFNKx62) *****\u001b[0m\n", + "Moved knight (β™˜) from g1 to f3.\n", "\u001b[32m**********************************************************************\u001b[0m\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[31m\n", ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", - "\u001b[33mPlayer Black\u001b[0m (to Board Proxy):\n", + "\u001b[33mPlayer White\u001b[0m (to Board Proxy):\n", "\n", - "I've moved my queen from d4 to d5. Your move!\n", + "I've moved my knight from g1 to f3. Your move!\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayer Black\u001b[0m (to Player White):\n", + "\u001b[33mPlayer White\u001b[0m (to Player Black):\n", "\n", - "I've moved my queen from d4 to d5. Your move!\n", + "I've moved my knight from g1 to f3. Your move!\n", "\n", "--------------------------------------------------------------------------------\n" ] @@ -1115,8 +1056,8 @@ "# Clear the board.\n", "board = chess.Board()\n", "\n", - "chat_result = player_white.initiate_chat(\n", - " player_black,\n", + "chat_result = player_black.initiate_chat(\n", + " player_white,\n", " message=\"Let's play chess! Your move.\",\n", " max_turns=4,\n", ")" @@ -1128,7 +1069,7 @@ "source": [ "In the output above, you can see \"Start a new chat\" is displayed\n", "whenever a new nested chat is started between the board proxy agent and a player agent.\n", - "The \"carryover\" empty as it is a new chat in the sequence." + "The \"carryover\" is empty as it is a new chat in the sequence." ] } ], @@ -1136,7 +1077,9 @@ "front_matter": { "description": "LLM-backed agents playing chess with each other using nested chats.", "tags": [ - "nested chat" + "nested chat", + "tool use", + "orchestration" ] }, "kernelspec": { @@ -1154,7 +1097,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.5" + "version": "3.12.0" } }, "nbformat": 4, diff --git a/notebook/agentchat_nested_sequential_chats.ipynb b/notebook/agentchat_nested_sequential_chats.ipynb index 1b6ff0ea62f..4d013218d32 100644 --- a/notebook/agentchat_nested_sequential_chats.ipynb +++ b/notebook/agentchat_nested_sequential_chats.ipynb @@ -811,7 +811,7 @@ "front_matter": { "description": "Solve complex tasks with one or more sequence chats nested as inner monologue.", "tags": [ - "nested chat", "sequential chat" + "nested chat", "sequential chats", "orchestration" ] }, "kernelspec": { diff --git a/notebook/agentchat_nestedchat.ipynb b/notebook/agentchat_nestedchat.ipynb index 7c48813aa59..3cd4d0a99ed 100644 --- a/notebook/agentchat_nestedchat.ipynb +++ b/notebook/agentchat_nestedchat.ipynb @@ -31,9 +31,10 @@ "metadata": {}, "outputs": [], "source": [ - "import autogen\n", "from typing_extensions import Annotated\n", "\n", + "import autogen\n", + "\n", "config_list = autogen.config_list_from_json(env_or_file=\"OAI_CONFIG_LIST\")\n", "llm_config = {\"config_list\": config_list}" ] @@ -656,13 +657,15 @@ } ], "metadata": { - "extra_files_to_copy": [ - "nested_chat_1.png", "nested_chat_2.png" - ], + "extra_files_to_copy": [ + "nested_chat_1.png", + "nested_chat_2.png" + ], "front_matter": { "description": "Solve complex tasks with a chat nested as inner monologue.", "tags": [ - "nested chat" + "nested chat", + "orchestration" ] }, "kernelspec": { diff --git a/notebook/agentchat_nestedchat_optiguide.ipynb b/notebook/agentchat_nestedchat_optiguide.ipynb index 52210cbe52b..c1648bce62b 100644 --- a/notebook/agentchat_nestedchat_optiguide.ipynb +++ b/notebook/agentchat_nestedchat_optiguide.ipynb @@ -36,18 +36,19 @@ "metadata": {}, "outputs": [], "source": [ - "import autogen\n", + "import re\n", "from typing import Union\n", "\n", + "# import auxiliary packages\n", + "import requests # for loading the example source code\n", + "from eventlet.timeout import Timeout\n", + "\n", "# test Gurobi installation\n", "from gurobipy import GRB\n", - "from eventlet.timeout import Timeout\n", - "import re\n", "from termcolor import colored\n", - "from autogen.code_utils import extract_code\n", "\n", - "# import auxiliary packages\n", - "import requests # for loading the example source code\n", + "import autogen\n", + "from autogen.code_utils import extract_code\n", "\n", "config_list_gpt4 = autogen.config_list_from_json(\n", " \"OAI_CONFIG_LIST\",\n", @@ -1121,7 +1122,9 @@ "front_matter": { "description": "This is a nested chat re-implementation of OptiGuide which is an LLM-based supply chain optimization framework.", "tags": [ - "nested chat" + "nested chat", + "hierarchical chat", + "orchestration" ] }, "kernelspec": { diff --git a/notebook/agentchat_oai_assistant_function_call.ipynb b/notebook/agentchat_oai_assistant_function_call.ipynb index 878175420c6..bc78819fb19 100644 --- a/notebook/agentchat_oai_assistant_function_call.ipynb +++ b/notebook/agentchat_oai_assistant_function_call.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Chat with OpenAI Assistant using function call in AutoGen: OSS Insights for Advanced GitHub Data Analysis\n", + "# Chat with OpenAI Assistant using function call in AutoGen: OSS Insights for Advanced GitHub Data Analysis\n", "\n", "This Jupyter Notebook demonstrates how to leverage OSS Insight (Open Source Software Insight) for advanced GitHub data analysis by defining `Function calls` in AutoGen for the OpenAI Assistant. \n", "\n", @@ -14,12 +14,19 @@ "2. Defining an OpenAI Assistant Agent in AutoGen\n", "3. Fetching GitHub Insight Data using Function Call\n", "\n", - "### Requirements\n", + "## Requirements\n", "\n", "AutoGen requires `Python>=3.8`. To run this notebook example, please install:\n", + "````{=mdx}\n", + ":::info Requirements\n", + "Install `pyautogen`:\n", "```bash\n", "pip install pyautogen\n", - "```" + "```\n", + "\n", + "For more information, please refer to the [installation guide](/docs/installation/).\n", + ":::\n", + "````" ] }, { @@ -36,7 +43,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Function Schema and Implementation\n", + "## Function Schema and Implementation\n", "\n", "This section provides the function schema definition and their implementation details. These functions are tailored to fetch and process data from GitHub, utilizing OSS Insight's capabilities." ] @@ -101,7 +108,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Defining an OpenAI Assistant Agent in AutoGen\n", + "## Defining an OpenAI Assistant Agent in AutoGen\n", "\n", "Here, we explore how to define an OpenAI Assistant Agent within the AutoGen. This includes setting up the agent to make use of the previously defined function calls for data retrieval and analysis." ] @@ -159,7 +166,18 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Fetching GitHub Insight Data using Function Call\n", + "````{=mdx}\n", + ":::tip\n", + "Learn more about configuring LLMs for agents [here](/docs/topics/llm_configuration).\n", + ":::\n", + "````\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Fetching GitHub Insight Data using Function Call\n", "\n", "This part of the notebook demonstrates the practical application of the defined functions and the OpenAI Assistant Agent in fetching and interpreting GitHub Insight data." ] @@ -256,6 +274,13 @@ } ], "metadata": { + "front_matter": { + "description": "This Jupyter Notebook demonstrates how to leverage OSS Insight (Open Source Software Insight) for advanced GitHub data analysis by defining `Function calls` in AutoGen for the OpenAI Assistant.", + "tags": [ + "OpenAI Assistant", + "function call" + ] + }, "kernelspec": { "display_name": "autogen", "language": "python", diff --git a/notebook/agentchat_oai_assistant_groupchat.ipynb b/notebook/agentchat_oai_assistant_groupchat.ipynb index 603d2cf71d9..d38fed4cdae 100644 --- a/notebook/agentchat_oai_assistant_groupchat.ipynb +++ b/notebook/agentchat_oai_assistant_groupchat.ipynb @@ -14,9 +14,16 @@ "## Requirements\n", "\n", "AutoGen requires `Python>=3.8`. To run this notebook example, please install:\n", + "````{=mdx}\n", + ":::info Requirements\n", + "Install `pyautogen`:\n", "```bash\n", - "pip install \"pyautogen>=0.2.3\"\n", - "```" + "pip install pyautogen\n", + "```\n", + "\n", + "For more information, please refer to the [installation guide](/docs/installation/).\n", + ":::\n", + "````" ] }, { @@ -50,19 +57,11 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "It first looks for environment variable \"OAI_CONFIG_LIST\" which needs to be a valid json string. If that variable is not found, it then looks for a json file named \"OAI_CONFIG_LIST\". It filters the configs by models (you can filter by other keys as well).\n", - "\n", - "The config list looks like the following:\n", - "```python\n", - "config_list = [\n", - " {\n", - " \"model\": \"gpt-4\",\n", - " \"api_key\": \"\",\n", - " }, # OpenAI API endpoint for gpt-4\n", - "]\n", - "```\n", - "\n", - "Currently Azure OpenAI does not support assistant api. You can set the value of config_list in any way you prefer. Please refer to this [notebook](https://github.com/microsoft/autogen/blob/main/website/docs/topics/llm_configuration.ipynb) for full code examples of the different methods." + "````{=mdx}\n", + ":::tip\n", + "Learn more about configuring LLMs for agents [here](/docs/topics/llm_configuration).\n", + ":::\n", + "````" ] }, { @@ -482,6 +481,13 @@ } ], "metadata": { + "front_matter": { + "description": "This Jupyter Notebook demonstrates how to use the GPTAssistantAgent in AutoGen's group chat mode, enabling collaborative task performance through automated chat with agents powered by LLMs, tools, or humans.", + "tags": [ + "OpenAI Assistant", + "group chat" + ] + }, "kernelspec": { "display_name": "Python 3", "language": "python", diff --git a/notebook/agentchat_oai_code_interpreter.ipynb b/notebook/agentchat_oai_code_interpreter.ipynb index 921165fdd6b..a8aeb614789 100644 --- a/notebook/agentchat_oai_code_interpreter.ipynb +++ b/notebook/agentchat_oai_code_interpreter.ipynb @@ -10,9 +10,16 @@ "## Requirements\n", "\n", "AutoGen requires `Python>=3.8`. To run this notebook example, please install:\n", + "````{=mdx}\n", + ":::info Requirements\n", + "Install `pyautogen`:\n", "```bash\n", "pip install pyautogen\n", - "```" + "```\n", + "\n", + "For more information, please refer to the [installation guide](/docs/installation/).\n", + ":::\n", + "````" ] }, { @@ -52,19 +59,11 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "It first looks for environment variable \"OAI_CONFIG_LIST\" which needs to be a valid json string. If that variable is not found, it then looks for a json file named \"OAI_CONFIG_LIST\". It filters the configs by models (you can filter by other keys as well).\n", - "\n", - "The config list looks like the following:\n", - "```python\n", - "config_list = [\n", - " {\n", - " \"model\": \"gpt-4\",\n", - " \"api_key\": \"\",\n", - " }, # OpenAI API endpoint for gpt-4\n", - "]\n", - "```\n", - "\n", - "Currently Azure OpenAi does not support assistant api. You can set the value of config_list in any way you prefer. Please refer to this [notebook](https://github.com/microsoft/autogen/blob/main/website/docs/llm_endpoint_configuration.ipynb) for full code examples of the different methods." + "````{=mdx}\n", + ":::tip\n", + "Learn more about configuring LLMs for agents [here](/docs/topics/llm_configuration).\n", + ":::\n", + "````" ] }, { @@ -297,6 +296,13 @@ } ], "metadata": { + "front_matter": { + "description": "This Jupyter Notebook showcases the integration of the Code Interpreter tool which executes Python code dynamically within applications.", + "tags": [ + "OpenAI Assistant", + "code interpreter" + ] + }, "kernelspec": { "display_name": "Python 3", "language": "python", diff --git a/notebook/agentchat_pgvector_RetrieveChat.ipynb b/notebook/agentchat_pgvector_RetrieveChat.ipynb new file mode 100644 index 00000000000..068ea55c7fc --- /dev/null +++ b/notebook/agentchat_pgvector_RetrieveChat.ipynb @@ -0,0 +1,1469 @@ +{ + "cells": [ + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Using RetrieveChat Powered by PGVector for Retrieve Augmented Code Generation and Question Answering\n", + "\n", + "AutoGen offers conversable agents powered by LLM, tool or human, which can be used to perform tasks collectively via automated chat. This framework allows tool use and human participation through multi-agent conversation.\n", + "Please find documentation about this feature [here](https://microsoft.github.io/autogen/docs/Use-Cases/agent_chat).\n", + "\n", + "RetrieveChat is a conversational system for retrieval-augmented code generation and question answering. In this notebook, we demonstrate how to utilize RetrieveChat to generate code and answer questions based on customized documentations that are not present in the LLM's training dataset. RetrieveChat uses the `RetrieveAssistantAgent` and `RetrieveUserProxyAgent`, which is similar to the usage of `AssistantAgent` and `UserProxyAgent` in other notebooks (e.g., [Automated Task Solving with Code Generation, Execution & Debugging](https://github.com/microsoft/autogen/blob/main/notebook/agentchat_auto_feedback_from_code_execution.ipynb)). Essentially, `RetrieveAssistantAgent` and `RetrieveUserProxyAgent` implement a different auto-reply mechanism corresponding to the RetrieveChat prompts.\n", + "\n", + "## Table of Contents\n", + "We'll demonstrate six examples of using RetrieveChat for code generation and question answering:\n", + "\n", + "- [Example 1: Generate code based off docstrings w/o human feedback](#example-1)\n", + "- [Example 2: Answer a question based off docstrings w/o human feedback](#example-2)\n", + "\n", + "\n", + "````{=mdx}\n", + ":::info Requirements\n", + "Some extra dependencies are needed for this notebook, which can be installed via pip:\n", + "\n", + "```bash\n", + "pip install pyautogen[retrievechat-pgvector] flaml[automl]\n", + "```\n", + "\n", + "For more information, please refer to the [installation guide](/docs/installation/).\n", + ":::\n", + "````\n", + "\n", + "Ensure you have a PGVector instance. \n", + "\n", + "If not, a test version can quickly be deployed using Docker.\n", + "\n", + "`docker-compose.yml`\n", + "```yml\n", + "version: '3.9'\n", + "\n", + "services:\n", + " db:\n", + " hostname: db\n", + " image: ankane/pgvector\n", + " ports:\n", + " - 5432:5432\n", + " restart: always\n", + " environment:\n", + " - POSTGRES_DB=postgres\n", + " - POSTGRES_USER=postgres\n", + " - POSTGRES_PASSWORD=postgres\n", + " - POSTGRES_HOST_AUTH_METHOD=trust\n", + " volumes:\n", + " - ./init.sql:/docker-entrypoint-initdb.d/init.sql\n", + "```\n", + "\n", + "Create `init.sql` file\n", + "```SQL\n", + "CREATE EXTENSION IF NOT EXISTS vector;\n", + "```\n" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Set your API Endpoint\n", + "\n", + "The [`config_list_from_json`](https://microsoft.github.io/autogen/docs/reference/oai/openai_utils#config_list_from_json) function loads a list of configurations from an environment variable or a json file.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "models to use: ['Meta-Llama-3-8B-Instruct-imatrix', 'gpt-3.5-turbo-0125', 'gpt-35-turbo']\n" + ] + } + ], + "source": [ + "import json\n", + "import os\n", + "\n", + "import chromadb\n", + "\n", + "import autogen\n", + "from autogen.agentchat.contrib.retrieve_assistant_agent import RetrieveAssistantAgent\n", + "from autogen.agentchat.contrib.retrieve_user_proxy_agent import RetrieveUserProxyAgent\n", + "\n", + "# Accepted file formats for that can be stored in\n", + "# a vector database instance\n", + "from autogen.retrieve_utils import TEXT_FORMATS\n", + "\n", + "config_list = [\n", + " {\n", + " \"model\": \"Meta-Llama-3-8B-Instruct-imatrix\",\n", + " \"api_key\": \"YOUR_API_KEY\",\n", + " \"base_url\": \"http://localhost:8080/v1\",\n", + " \"api_type\": \"openai\",\n", + " },\n", + " {\"model\": \"gpt-3.5-turbo-0125\", \"api_key\": \"YOUR_API_KEY\", \"api_type\": \"openai\"},\n", + " {\n", + " \"model\": \"gpt-35-turbo\",\n", + " \"base_url\": \"...\",\n", + " \"api_type\": \"azure\",\n", + " \"api_version\": \"2023-07-01-preview\",\n", + " \"api_key\": \"...\",\n", + " },\n", + "]\n", + "\n", + "assert len(config_list) > 0\n", + "print(\"models to use: \", [config_list[i][\"model\"] for i in range(len(config_list))])" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "````{=mdx}\n", + ":::tip\n", + "Learn more about configuring LLMs for agents [here](/docs/topics/llm_configuration).\n", + ":::\n", + "````\n", + "\n", + "## Construct agents for RetrieveChat\n", + "\n", + "We start by initializing the `RetrieveAssistantAgent` and `RetrieveUserProxyAgent`. The system message needs to be set to \"You are a helpful assistant.\" for RetrieveAssistantAgent. The detailed instructions are given in the user message. Later we will use the `RetrieveUserProxyAgent.message_generator` to combine the instructions and a retrieval augmented generation task for an initial prompt to be sent to the LLM assistant." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Accepted file formats for `docs_path`:\n", + "['org', 'pdf', 'md', 'docx', 'epub', 'rst', 'rtf', 'xml', 'ppt', 'txt', 'jsonl', 'msg', 'htm', 'yaml', 'html', 'xlsx', 'log', 'yml', 'odt', 'tsv', 'doc', 'pptx', 'csv', 'json']\n" + ] + } + ], + "source": [ + "print(\"Accepted file formats for `docs_path`:\")\n", + "print(TEXT_FORMATS)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages/torch/cuda/__init__.py:141: UserWarning: CUDA initialization: The NVIDIA driver on your system is too old (found version 11060). Please update your GPU driver by downloading and installing a new version from the URL: http://www.nvidia.com/Download/index.aspx Alternatively, go to: https://pytorch.org to install a PyTorch version that has been compiled with your version of the CUDA driver. (Triggered internally at ../c10/cuda/CUDAFunctions.cpp:108.)\n", + " return torch._C._cuda_getDeviceCount() > 0\n" + ] + } + ], + "source": [ + "# 1. create an RetrieveAssistantAgent instance named \"assistant\"\n", + "assistant = RetrieveAssistantAgent(\n", + " name=\"assistant\",\n", + " system_message=\"You are a helpful assistant.\",\n", + " llm_config={\n", + " \"timeout\": 600,\n", + " \"cache_seed\": 42,\n", + " \"config_list\": config_list,\n", + " },\n", + ")\n", + "\n", + "# 2. create the RetrieveUserProxyAgent instance named \"ragproxyagent\"\n", + "# By default, the human_input_mode is \"ALWAYS\", which means the agent will ask for human input at every step. We set it to \"NEVER\" here.\n", + "# `docs_path` is the path to the docs directory. It can also be the path to a single file, or the url to a single file. By default,\n", + "# it is set to None, which works only if the collection is already created.\n", + "# `task` indicates the kind of task we're working on. In this example, it's a `code` task.\n", + "# `chunk_token_size` is the chunk token size for the retrieve chat. By default, it is set to `max_tokens * 0.6`, here we set it to 2000.\n", + "# `custom_text_types` is a list of file types to be processed. Default is `autogen.retrieve_utils.TEXT_FORMATS`.\n", + "# This only applies to files under the directories in `docs_path`. Explicitly included files and urls will be chunked regardless of their types.\n", + "# In this example, we set it to [\"non-existent-type\"] to only process markdown files. Since no \"non-existent-type\" files are included in the `websit/docs`,\n", + "# no files there will be processed. However, the explicitly included urls will still be processed.\n", + "ragproxyagent = RetrieveUserProxyAgent(\n", + " name=\"ragproxyagent\",\n", + " human_input_mode=\"NEVER\",\n", + " max_consecutive_auto_reply=1,\n", + " retrieve_config={\n", + " \"task\": \"code\",\n", + " \"docs_path\": [\n", + " \"https://raw.githubusercontent.com/microsoft/FLAML/main/website/docs/Examples/Integrate%20-%20Spark.md\",\n", + " \"https://raw.githubusercontent.com/microsoft/FLAML/main/website/docs/Research.md\",\n", + " os.path.join(os.path.abspath(\"\"), \"..\", \"website\", \"docs\"),\n", + " ],\n", + " \"custom_text_types\": [\"non-existent-type\"],\n", + " \"chunk_token_size\": 2000,\n", + " \"model\": config_list[0][\"model\"],\n", + " \"vector_db\": \"pgvector\", # PGVector database\n", + " \"collection_name\": \"flaml_collection\",\n", + " \"db_config\": {\n", + " \"connection_string\": \"postgresql://postgres:postgres@localhost:5432/postgres\", # Optional - connect to an external vector database\n", + " # \"host\": postgres, # Optional vector database host\n", + " # \"port\": 5432, # Optional vector database port\n", + " # \"database\": postgres, # Optional vector database name\n", + " # \"username\": postgres, # Optional vector database username\n", + " # \"password\": postgres, # Optional vector database password\n", + " \"model_name\": \"all-MiniLM-L6-v2\", # Sentence embedding model from https://huggingface.co/models?library=sentence-transformers or https://www.sbert.net/docs/pretrained_models.html\n", + " },\n", + " \"get_or_create\": True, # set to False if you don't want to reuse an existing collection\n", + " \"overwrite\": False, # set to True if you want to overwrite an existing collection\n", + " },\n", + " code_execution_config=False, # set to False if you don't want to execute the code\n", + ")" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Example 1\n", + "\n", + "[Back to top](#table-of-contents)\n", + "\n", + "Use RetrieveChat to help generate sample code and automatically run the code and fix errors if there is any.\n", + "\n", + "Problem: Which API should I use if I want to use FLAML for a classification task and I want to train the model in 30 seconds. Use spark to parallel the training. Force cancel jobs if time limit is reached." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2024-04-25 11:23:53,000 - autogen.agentchat.contrib.retrieve_user_proxy_agent - INFO - \u001b[32mUse the existing collection `flaml_collection`.\u001b[0m\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Trying to create collection.\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2024-04-25 11:23:54,745 - autogen.agentchat.contrib.retrieve_user_proxy_agent - INFO - Found 2 chunks.\u001b[0m\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "VectorDB returns doc_ids: [['bdfbc921', '7968cf3c']]\n", + "\u001b[32mAdding content of doc bdfbc921 to context.\u001b[0m\n", + "\u001b[32mAdding content of doc 7968cf3c to context.\u001b[0m\n", + "\u001b[33mragproxyagent\u001b[0m (to assistant):\n", + "\n", + "You're a retrieve augmented coding assistant. You answer user's questions based on your own knowledge and the\n", + "context provided by the user.\n", + "If you can't answer the question with or without the current context, you should reply exactly `UPDATE CONTEXT`.\n", + "For code generation, you must obey the following rules:\n", + "Rule 1. You MUST NOT install any packages because all the packages needed are already installed.\n", + "Rule 2. You must follow the formats below to write your code:\n", + "```language\n", + "# your code\n", + "```\n", + "\n", + "User's question is: How can I use FLAML to perform a classification task and use spark to do parallel training. Train for 30 seconds and force cancel jobs if time limit is reached.\n", + "\n", + "Context is: # Integrate - Spark\n", + "\n", + "FLAML has integrated Spark for distributed training. There are two main aspects of integration with Spark:\n", + "\n", + "- Use Spark ML estimators for AutoML.\n", + "- Use Spark to run training in parallel spark jobs.\n", + "\n", + "## Spark ML Estimators\n", + "\n", + "FLAML integrates estimators based on Spark ML models. These models are trained in parallel using Spark, so we called them Spark estimators. To use these models, you first need to organize your data in the required format.\n", + "\n", + "### Data\n", + "\n", + "For Spark estimators, AutoML only consumes Spark data. FLAML provides a convenient function `to_pandas_on_spark` in the `flaml.automl.spark.utils` module to convert your data into a pandas-on-spark (`pyspark.pandas`) dataframe/series, which Spark estimators require.\n", + "\n", + "This utility function takes data in the form of a `pandas.Dataframe` or `pyspark.sql.Dataframe` and converts it into a pandas-on-spark dataframe. It also takes `pandas.Series` or `pyspark.sql.Dataframe` and converts it into a [pandas-on-spark](https://spark.apache.org/docs/latest/api/python/user_guide/pandas_on_spark/index.html) series. If you pass in a `pyspark.pandas.Dataframe`, it will not make any changes.\n", + "\n", + "This function also accepts optional arguments `index_col` and `default_index_type`.\n", + "\n", + "- `index_col` is the column name to use as the index, default is None.\n", + "- `default_index_type` is the default index type, default is \"distributed-sequence\". More info about default index type could be found on Spark official [documentation](https://spark.apache.org/docs/latest/api/python/user_guide/pandas_on_spark/options.html#default-index-type)\n", + "\n", + "Here is an example code snippet for Spark Data:\n", + "\n", + "```python\n", + "import pandas as pd\n", + "from flaml.automl.spark.utils import to_pandas_on_spark\n", + "\n", + "# Creating a dictionary\n", + "data = {\n", + " \"Square_Feet\": [800, 1200, 1800, 1500, 850],\n", + " \"Age_Years\": [20, 15, 10, 7, 25],\n", + " \"Price\": [100000, 200000, 300000, 240000, 120000],\n", + "}\n", + "\n", + "# Creating a pandas DataFrame\n", + "dataframe = pd.DataFrame(data)\n", + "label = \"Price\"\n", + "\n", + "# Convert to pandas-on-spark dataframe\n", + "psdf = to_pandas_on_spark(dataframe)\n", + "```\n", + "\n", + "To use Spark ML models you need to format your data appropriately. Specifically, use [`VectorAssembler`](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.ml.feature.VectorAssembler.html) to merge all feature columns into a single vector column.\n", + "\n", + "Here is an example of how to use it:\n", + "\n", + "```python\n", + "from pyspark.ml.feature import VectorAssembler\n", + "\n", + "columns = psdf.columns\n", + "feature_cols = [col for col in columns if col != label]\n", + "featurizer = VectorAssembler(inputCols=feature_cols, outputCol=\"features\")\n", + "psdf = featurizer.transform(psdf.to_spark(index_col=\"index\"))[\"index\", \"features\"]\n", + "```\n", + "\n", + "Later in conducting the experiment, use your pandas-on-spark data like non-spark data and pass them using `X_train, y_train` or `dataframe, label`.\n", + "\n", + "### Estimators\n", + "\n", + "#### Model List\n", + "\n", + "- `lgbm_spark`: The class for fine-tuning Spark version LightGBM models, using [SynapseML](https://microsoft.github.io/SynapseML/docs/features/lightgbm/about/) API.\n", + "\n", + "#### Usage\n", + "\n", + "First, prepare your data in the required format as described in the previous section.\n", + "\n", + "By including the models you intend to try in the `estimators_list` argument to `flaml.automl`, FLAML will start trying configurations for these models. If your input is Spark data, FLAML will also use estimators with the `_spark` postfix by default, even if you haven't specified them.\n", + "\n", + "Here is an example code snippet using SparkML models in AutoML:\n", + "\n", + "```python\n", + "import flaml\n", + "\n", + "# prepare your data in pandas-on-spark format as we previously mentioned\n", + "\n", + "automl = flaml.AutoML()\n", + "settings = {\n", + " \"time_budget\": 30,\n", + " \"metric\": \"r2\",\n", + " \"estimator_list\": [\"lgbm_spark\"], # this setting is optional\n", + " \"task\": \"regression\",\n", + "}\n", + "\n", + "automl.fit(\n", + " dataframe=psdf,\n", + " label=label,\n", + " **settings,\n", + ")\n", + "```\n", + "\n", + "[Link to notebook](https://github.com/microsoft/FLAML/blob/main/notebook/automl_bankrupt_synapseml.ipynb) | [Open in colab](https://colab.research.google.com/github/microsoft/FLAML/blob/main/notebook/automl_bankrupt_synapseml.ipynb)\n", + "\n", + "## Parallel Spark Jobs\n", + "\n", + "You can activate Spark as the parallel backend during parallel tuning in both [AutoML](/docs/Use-Cases/Task-Oriented-AutoML#parallel-tuning) and [Hyperparameter Tuning](/docs/Use-Cases/Tune-User-Defined-Function#parallel-tuning), by setting the `use_spark` to `true`. FLAML will dispatch your job to the distributed Spark backend using [`joblib-spark`](https://github.com/joblib/joblib-spark).\n", + "\n", + "Please note that you should not set `use_spark` to `true` when applying AutoML and Tuning for Spark Data. This is because only SparkML models will be used for Spark Data in AutoML and Tuning. As SparkML models run in parallel, there is no need to distribute them with `use_spark` again.\n", + "\n", + "All the Spark-related arguments are stated below. These arguments are available in both Hyperparameter Tuning and AutoML:\n", + "\n", + "- `use_spark`: boolean, default=False | Whether to use spark to run the training in parallel spark jobs. This can be used to accelerate training on large models and large datasets, but will incur more overhead in time and thus slow down training in some cases. GPU training is not supported yet when use_spark is True. For Spark clusters, by default, we will launch one trial per executor. However, sometimes we want to launch more trials than the number of executors (e.g., local mode). In this case, we can set the environment variable `FLAML_MAX_CONCURRENT` to override the detected `num_executors`. The final number of concurrent trials will be the minimum of `n_concurrent_trials` and `num_executors`.\n", + "- `n_concurrent_trials`: int, default=1 | The number of concurrent trials. When n_concurrent_trials > 1, FLAML performes parallel tuning.\n", + "- `force_cancel`: boolean, default=False | Whether to forcely cancel Spark jobs if the search time exceeded the time budget. Spark jobs include parallel tuning jobs and Spark-based model training jobs.\n", + "\n", + "An example code snippet for using parallel Spark jobs:\n", + "\n", + "```python\n", + "import flaml\n", + "\n", + "automl_experiment = flaml.AutoML()\n", + "automl_settings = {\n", + " \"time_budget\": 30,\n", + " \"metric\": \"r2\",\n", + " \"task\": \"regression\",\n", + " \"n_concurrent_trials\": 2,\n", + " \"use_spark\": True,\n", + " \"force_cancel\": True, # Activating the force_cancel option can immediately halt Spark jobs once they exceed the allocated time_budget.\n", + "}\n", + "\n", + "automl.fit(\n", + " dataframe=dataframe,\n", + " label=label,\n", + " **automl_settings,\n", + ")\n", + "```\n", + "\n", + "[Link to notebook](https://github.com/microsoft/FLAML/blob/main/notebook/integrate_spark.ipynb) | [Open in colab](https://colab.research.google.com/github/microsoft/FLAML/blob/main/notebook/integrate_spark.ipynb)\n", + "# Research\n", + "\n", + "For technical details, please check our research publications.\n", + "\n", + "- [FLAML: A Fast and Lightweight AutoML Library](https://www.microsoft.com/en-us/research/publication/flaml-a-fast-and-lightweight-automl-library/). Chi Wang, Qingyun Wu, Markus Weimer, Erkang Zhu. MLSys 2021.\n", + "\n", + "```bibtex\n", + "@inproceedings{wang2021flaml,\n", + " title={FLAML: A Fast and Lightweight AutoML Library},\n", + " author={Chi Wang and Qingyun Wu and Markus Weimer and Erkang Zhu},\n", + " year={2021},\n", + " booktitle={MLSys},\n", + "}\n", + "```\n", + "\n", + "- [Frugal Optimization for Cost-related Hyperparameters](https://arxiv.org/abs/2005.01571). Qingyun Wu, Chi Wang, Silu Huang. AAAI 2021.\n", + "\n", + "```bibtex\n", + "@inproceedings{wu2021cfo,\n", + " title={Frugal Optimization for Cost-related Hyperparameters},\n", + " author={Qingyun Wu and Chi Wang and Silu Huang},\n", + " year={2021},\n", + " booktitle={AAAI},\n", + "}\n", + "```\n", + "\n", + "- [Economical Hyperparameter Optimization With Blended Search Strategy](https://www.microsoft.com/en-us/research/publication/economical-hyperparameter-optimization-with-blended-search-strategy/). Chi Wang, Qingyun Wu, Silu Huang, Amin Saied. ICLR 2021.\n", + "\n", + "```bibtex\n", + "@inproceedings{wang2021blendsearch,\n", + " title={Economical Hyperparameter Optimization With Blended Search Strategy},\n", + " author={Chi Wang and Qingyun Wu and Silu Huang and Amin Saied},\n", + " year={2021},\n", + " booktitle={ICLR},\n", + "}\n", + "```\n", + "\n", + "- [An Empirical Study on Hyperparameter Optimization for Fine-Tuning Pre-trained Language Models](https://aclanthology.org/2021.acl-long.178.pdf). Susan Xueqing Liu, Chi Wang. ACL 2021.\n", + "\n", + "```bibtex\n", + "@inproceedings{liuwang2021hpolm,\n", + " title={An Empirical Study on Hyperparameter Optimization for Fine-Tuning Pre-trained Language Models},\n", + " author={Susan Xueqing Liu and Chi Wang},\n", + " year={2021},\n", + " booktitle={ACL},\n", + "}\n", + "```\n", + "\n", + "- [ChaCha for Online AutoML](https://www.microsoft.com/en-us/research/publication/chacha-for-online-automl/). Qingyun Wu, Chi Wang, John Langford, Paul Mineiro and Marco Rossi. ICML 2021.\n", + "\n", + "```bibtex\n", + "@inproceedings{wu2021chacha,\n", + " title={ChaCha for Online AutoML},\n", + " author={Qingyun Wu and Chi Wang and John Langford and Paul Mineiro and Marco Rossi},\n", + " year={2021},\n", + " booktitle={ICML},\n", + "}\n", + "```\n", + "\n", + "- [Fair AutoML](https://arxiv.org/abs/2111.06495). Qingyun Wu, Chi Wang. ArXiv preprint arXiv:2111.06495 (2021).\n", + "\n", + "```bibtex\n", + "@inproceedings{wuwang2021fairautoml,\n", + " title={Fair AutoML},\n", + " author={Qingyun Wu and Chi Wang},\n", + " year={2021},\n", + " booktitle={ArXiv preprint arXiv:2111.06495},\n", + "}\n", + "```\n", + "\n", + "- [Mining Robust Default Configurations for Resource-constrained AutoML](https://arxiv.org/abs/2202.09927). Moe Kayali, Chi Wang. ArXiv preprint arXiv:2202.09927 (2022).\n", + "\n", + "```bibtex\n", + "@inproceedings{kayaliwang2022default,\n", + " title={Mining Robust Default Configurations for Resource-constrained AutoML},\n", + " author={Moe Kayali and Chi Wang},\n", + " year={2022},\n", + " booktitle={ArXiv preprint arXiv:2202.09927},\n", + "}\n", + "```\n", + "\n", + "- [Targeted Hyperparameter Optimization with Lexicographic Preferences Over Multiple Objectives](https://openreview.net/forum?id=0Ij9_q567Ma). Shaokun Zhang, Feiran Jia, Chi Wang, Qingyun Wu. ICLR 2023 (notable-top-5%).\n", + "\n", + "```bibtex\n", + "@inproceedings{zhang2023targeted,\n", + " title={Targeted Hyperparameter Optimization with Lexicographic Preferences Over Multiple Objectives},\n", + " author={Shaokun Zhang and Feiran Jia and Chi Wang and Qingyun Wu},\n", + " booktitle={International Conference on Learning Representations},\n", + " year={2023},\n", + " url={https://openreview.net/forum?id=0Ij9_q567Ma},\n", + "}\n", + "```\n", + "\n", + "- [Cost-Effective Hyperparameter Optimization for Large Language Model Generation Inference](https://arxiv.org/abs/2303.04673). Chi Wang, Susan Xueqing Liu, Ahmed H. Awadallah. ArXiv preprint arXiv:2303.04673 (2023).\n", + "\n", + "```bibtex\n", + "@inproceedings{wang2023EcoOptiGen,\n", + " title={Cost-Effective Hyperparameter Optimization for Large Language Model Generation Inference},\n", + " author={Chi Wang and Susan Xueqing Liu and Ahmed H. Awadallah},\n", + " year={2023},\n", + " booktitle={ArXiv preprint arXiv:2303.04673},\n", + "}\n", + "```\n", + "\n", + "- [An Empirical Study on Challenging Math Problem Solving with GPT-4](https://arxiv.org/abs/2306.01337). Yiran Wu, Feiran Jia, Shaokun Zhang, Hangyu Li, Erkang Zhu, Yue Wang, Yin Tat Lee, Richard Peng, Qingyun Wu, Chi Wang. ArXiv preprint arXiv:2306.01337 (2023).\n", + "\n", + "```bibtex\n", + "@inproceedings{wu2023empirical,\n", + " title={An Empirical Study on Challenging Math Problem Solving with GPT-4},\n", + " author={Yiran Wu and Feiran Jia and Shaokun Zhang and Hangyu Li and Erkang Zhu and Yue Wang and Yin Tat Lee and Richard Peng and Qingyun Wu and Chi Wang},\n", + " year={2023},\n", + " booktitle={ArXiv preprint arXiv:2306.01337},\n", + "}\n", + "```\n", + "\n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[32mAdding content of doc 7968cf3c to context.\u001b[0m\n", + "\u001b[33mragproxyagent\u001b[0m (to assistant):\n", + "\n", + "You're a retrieve augmented coding assistant. You answer user's questions based on your own knowledge and the\n", + "context provided by the user.\n", + "If you can't answer the question with or without the current context, you should reply exactly `UPDATE CONTEXT`.\n", + "For code generation, you must obey the following rules:\n", + "Rule 1. You MUST NOT install any packages because all the packages needed are already installed.\n", + "Rule 2. You must follow the formats below to write your code:\n", + "```language\n", + "# your code\n", + "```\n", + "\n", + "User's question is: How can I use FLAML to perform a classification task and use spark to do parallel training. Train for 30 seconds and force cancel jobs if time limit is reached.\n", + "\n", + "Context is: # Integrate - Spark\n", + "\n", + "FLAML has integrated Spark for distributed training. There are two main aspects of integration with Spark:\n", + "\n", + "- Use Spark ML estimators for AutoML.\n", + "- Use Spark to run training in parallel spark jobs.\n", + "\n", + "## Spark ML Estimators\n", + "\n", + "FLAML integrates estimators based on Spark ML models. These models are trained in parallel using Spark, so we called them Spark estimators. To use these models, you first need to organize your data in the required format.\n", + "\n", + "### Data\n", + "\n", + "For Spark estimators, AutoML only consumes Spark data. FLAML provides a convenient function `to_pandas_on_spark` in the `flaml.automl.spark.utils` module to convert your data into a pandas-on-spark (`pyspark.pandas`) dataframe/series, which Spark estimators require.\n", + "\n", + "This utility function takes data in the form of a `pandas.Dataframe` or `pyspark.sql.Dataframe` and converts it into a pandas-on-spark dataframe. It also takes `pandas.Series` or `pyspark.sql.Dataframe` and converts it into a [pandas-on-spark](https://spark.apache.org/docs/latest/api/python/user_guide/pandas_on_spark/index.html) series. If you pass in a `pyspark.pandas.Dataframe`, it will not make any changes.\n", + "\n", + "This function also accepts optional arguments `index_col` and `default_index_type`.\n", + "\n", + "- `index_col` is the column name to use as the index, default is None.\n", + "- `default_index_type` is the default index type, default is \"distributed-sequence\". More info about default index type could be found on Spark official [documentation](https://spark.apache.org/docs/latest/api/python/user_guide/pandas_on_spark/options.html#default-index-type)\n", + "\n", + "Here is an example code snippet for Spark Data:\n", + "\n", + "```python\n", + "import pandas as pd\n", + "from flaml.automl.spark.utils import to_pandas_on_spark\n", + "\n", + "# Creating a dictionary\n", + "data = {\n", + " \"Square_Feet\": [800, 1200, 1800, 1500, 850],\n", + " \"Age_Years\": [20, 15, 10, 7, 25],\n", + " \"Price\": [100000, 200000, 300000, 240000, 120000],\n", + "}\n", + "\n", + "# Creating a pandas DataFrame\n", + "dataframe = pd.DataFrame(data)\n", + "label = \"Price\"\n", + "\n", + "# Convert to pandas-on-spark dataframe\n", + "psdf = to_pandas_on_spark(dataframe)\n", + "```\n", + "\n", + "To use Spark ML models you need to format your data appropriately. Specifically, use [`VectorAssembler`](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.ml.feature.VectorAssembler.html) to merge all feature columns into a single vector column.\n", + "\n", + "Here is an example of how to use it:\n", + "\n", + "```python\n", + "from pyspark.ml.feature import VectorAssembler\n", + "\n", + "columns = psdf.columns\n", + "feature_cols = [col for col in columns if col != label]\n", + "featurizer = VectorAssembler(inputCols=feature_cols, outputCol=\"features\")\n", + "psdf = featurizer.transform(psdf.to_spark(index_col=\"index\"))[\"index\", \"features\"]\n", + "```\n", + "\n", + "Later in conducting the experiment, use your pandas-on-spark data like non-spark data and pass them using `X_train, y_train` or `dataframe, label`.\n", + "\n", + "### Estimators\n", + "\n", + "#### Model List\n", + "\n", + "- `lgbm_spark`: The class for fine-tuning Spark version LightGBM models, using [SynapseML](https://microsoft.github.io/SynapseML/docs/features/lightgbm/about/) API.\n", + "\n", + "#### Usage\n", + "\n", + "First, prepare your data in the required format as described in the previous section.\n", + "\n", + "By including the models you intend to try in the `estimators_list` argument to `flaml.automl`, FLAML will start trying configurations for these models. If your input is Spark data, FLAML will also use estimators with the `_spark` postfix by default, even if you haven't specified them.\n", + "\n", + "Here is an example code snippet using SparkML models in AutoML:\n", + "\n", + "```python\n", + "import flaml\n", + "\n", + "# prepare your data in pandas-on-spark format as we previously mentioned\n", + "\n", + "automl = flaml.AutoML()\n", + "settings = {\n", + " \"time_budget\": 30,\n", + " \"metric\": \"r2\",\n", + " \"estimator_list\": [\"lgbm_spark\"], # this setting is optional\n", + " \"task\": \"regression\",\n", + "}\n", + "\n", + "automl.fit(\n", + " dataframe=psdf,\n", + " label=label,\n", + " **settings,\n", + ")\n", + "```\n", + "\n", + "[Link to notebook](https://github.com/microsoft/FLAML/blob/main/notebook/automl_bankrupt_synapseml.ipynb) | [Open in colab](https://colab.research.google.com/github/microsoft/FLAML/blob/main/notebook/automl_bankrupt_synapseml.ipynb)\n", + "\n", + "## Parallel Spark Jobs\n", + "\n", + "You can activate Spark as the parallel backend during parallel tuning in both [AutoML](/docs/Use-Cases/Task-Oriented-AutoML#parallel-tuning) and [Hyperparameter Tuning](/docs/Use-Cases/Tune-User-Defined-Function#parallel-tuning), by setting the `use_spark` to `true`. FLAML will dispatch your job to the distributed Spark backend using [`joblib-spark`](https://github.com/joblib/joblib-spark).\n", + "\n", + "Please note that you should not set `use_spark` to `true` when applying AutoML and Tuning for Spark Data. This is because only SparkML models will be used for Spark Data in AutoML and Tuning. As SparkML models run in parallel, there is no need to distribute them with `use_spark` again.\n", + "\n", + "All the Spark-related arguments are stated below. These arguments are available in both Hyperparameter Tuning and AutoML:\n", + "\n", + "- `use_spark`: boolean, default=False | Whether to use spark to run the training in parallel spark jobs. This can be used to accelerate training on large models and large datasets, but will incur more overhead in time and thus slow down training in some cases. GPU training is not supported yet when use_spark is True. For Spark clusters, by default, we will launch one trial per executor. However, sometimes we want to launch more trials than the number of executors (e.g., local mode). In this case, we can set the environment variable `FLAML_MAX_CONCURRENT` to override the detected `num_executors`. The final number of concurrent trials will be the minimum of `n_concurrent_trials` and `num_executors`.\n", + "- `n_concurrent_trials`: int, default=1 | The number of concurrent trials. When n_concurrent_trials > 1, FLAML performes parallel tuning.\n", + "- `force_cancel`: boolean, default=False | Whether to forcely cancel Spark jobs if the search time exceeded the time budget. Spark jobs include parallel tuning jobs and Spark-based model training jobs.\n", + "\n", + "An example code snippet for using parallel Spark jobs:\n", + "\n", + "```python\n", + "import flaml\n", + "\n", + "automl_experiment = flaml.AutoML()\n", + "automl_settings = {\n", + " \"time_budget\": 30,\n", + " \"metric\": \"r2\",\n", + " \"task\": \"regression\",\n", + " \"n_concurrent_trials\": 2,\n", + " \"use_spark\": True,\n", + " \"force_cancel\": True, # Activating the force_cancel option can immediately halt Spark jobs once they exceed the allocated time_budget.\n", + "}\n", + "\n", + "automl.fit(\n", + " dataframe=dataframe,\n", + " label=label,\n", + " **automl_settings,\n", + ")\n", + "```\n", + "\n", + "[Link to notebook](https://github.com/microsoft/FLAML/blob/main/notebook/integrate_spark.ipynb) | [Open in colab](https://colab.research.google.com/github/microsoft/FLAML/blob/main/notebook/integrate_spark.ipynb)\n", + "# Research\n", + "\n", + "For technical details, please check our research publications.\n", + "\n", + "- [FLAML: A Fast and Lightweight AutoML Library](https://www.microsoft.com/en-us/research/publication/flaml-a-fast-and-lightweight-automl-library/). Chi Wang, Qingyun Wu, Markus Weimer, Erkang Zhu. MLSys 2021.\n", + "\n", + "```bibtex\n", + "@inproceedings{wang2021flaml,\n", + " title={FLAML: A Fast and Lightweight AutoML Library},\n", + " author={Chi Wang and Qingyun Wu and Markus Weimer and Erkang Zhu},\n", + " year={2021},\n", + " booktitle={MLSys},\n", + "}\n", + "```\n", + "\n", + "- [Frugal Optimization for Cost-related Hyperparameters](https://arxiv.org/abs/2005.01571). Qingyun Wu, Chi Wang, Silu Huang. AAAI 2021.\n", + "\n", + "```bibtex\n", + "@inproceedings{wu2021cfo,\n", + " title={Frugal Optimization for Cost-related Hyperparameters},\n", + " author={Qingyun Wu and Chi Wang and Silu Huang},\n", + " year={2021},\n", + " booktitle={AAAI},\n", + "}\n", + "```\n", + "\n", + "- [Economical Hyperparameter Optimization With Blended Search Strategy](https://www.microsoft.com/en-us/research/publication/economical-hyperparameter-optimization-with-blended-search-strategy/). Chi Wang, Qingyun Wu, Silu Huang, Amin Saied. ICLR 2021.\n", + "\n", + "```bibtex\n", + "@inproceedings{wang2021blendsearch,\n", + " title={Economical Hyperparameter Optimization With Blended Search Strategy},\n", + " author={Chi Wang and Qingyun Wu and Silu Huang and Amin Saied},\n", + " year={2021},\n", + " booktitle={ICLR},\n", + "}\n", + "```\n", + "\n", + "- [An Empirical Study on Hyperparameter Optimization for Fine-Tuning Pre-trained Language Models](https://aclanthology.org/2021.acl-long.178.pdf). Susan Xueqing Liu, Chi Wang. ACL 2021.\n", + "\n", + "```bibtex\n", + "@inproceedings{liuwang2021hpolm,\n", + " title={An Empirical Study on Hyperparameter Optimization for Fine-Tuning Pre-trained Language Models},\n", + " author={Susan Xueqing Liu and Chi Wang},\n", + " year={2021},\n", + " booktitle={ACL},\n", + "}\n", + "```\n", + "\n", + "- [ChaCha for Online AutoML](https://www.microsoft.com/en-us/research/publication/chacha-for-online-automl/). Qingyun Wu, Chi Wang, John Langford, Paul Mineiro and Marco Rossi. ICML 2021.\n", + "\n", + "```bibtex\n", + "@inproceedings{wu2021chacha,\n", + " title={ChaCha for Online AutoML},\n", + " author={Qingyun Wu and Chi Wang and John Langford and Paul Mineiro and Marco Rossi},\n", + " year={2021},\n", + " booktitle={ICML},\n", + "}\n", + "```\n", + "\n", + "- [Fair AutoML](https://arxiv.org/abs/2111.06495). Qingyun Wu, Chi Wang. ArXiv preprint arXiv:2111.06495 (2021).\n", + "\n", + "```bibtex\n", + "@inproceedings{wuwang2021fairautoml,\n", + " title={Fair AutoML},\n", + " author={Qingyun Wu and Chi Wang},\n", + " year={2021},\n", + " booktitle={ArXiv preprint arXiv:2111.06495},\n", + "}\n", + "```\n", + "\n", + "- [Mining Robust Default Configurations for Resource-constrained AutoML](https://arxiv.org/abs/2202.09927). Moe Kayali, Chi Wang. ArXiv preprint arXiv:2202.09927 (2022).\n", + "\n", + "```bibtex\n", + "@inproceedings{kayaliwang2022default,\n", + " title={Mining Robust Default Configurations for Resource-constrained AutoML},\n", + " author={Moe Kayali and Chi Wang},\n", + " year={2022},\n", + " booktitle={ArXiv preprint arXiv:2202.09927},\n", + "}\n", + "```\n", + "\n", + "- [Targeted Hyperparameter Optimization with Lexicographic Preferences Over Multiple Objectives](https://openreview.net/forum?id=0Ij9_q567Ma). Shaokun Zhang, Feiran Jia, Chi Wang, Qingyun Wu. ICLR 2023 (notable-top-5%).\n", + "\n", + "```bibtex\n", + "@inproceedings{zhang2023targeted,\n", + " title={Targeted Hyperparameter Optimization with Lexicographic Preferences Over Multiple Objectives},\n", + " author={Shaokun Zhang and Feiran Jia and Chi Wang and Qingyun Wu},\n", + " booktitle={International Conference on Learning Representations},\n", + " year={2023},\n", + " url={https://openreview.net/forum?id=0Ij9_q567Ma},\n", + "}\n", + "```\n", + "\n", + "- [Cost-Effective Hyperparameter Optimization for Large Language Model Generation Inference](https://arxiv.org/abs/2303.04673). Chi Wang, Susan Xueqing Liu, Ahmed H. Awadallah. ArXiv preprint arXiv:2303.04673 (2023).\n", + "\n", + "```bibtex\n", + "@inproceedings{wang2023EcoOptiGen,\n", + " title={Cost-Effective Hyperparameter Optimization for Large Language Model Generation Inference},\n", + " author={Chi Wang and Susan Xueqing Liu and Ahmed H. Awadallah},\n", + " year={2023},\n", + " booktitle={ArXiv preprint arXiv:2303.04673},\n", + "}\n", + "```\n", + "\n", + "- [An Empirical Study on Challenging Math Problem Solving with GPT-4](https://arxiv.org/abs/2306.01337). Yiran Wu, Feiran Jia, Shaokun Zhang, Hangyu Li, Erkang Zhu, Yue Wang, Yin Tat Lee, Richard Peng, Qingyun Wu, Chi Wang. ArXiv preprint arXiv:2306.01337 (2023).\n", + "\n", + "```bibtex\n", + "@inproceedings{wu2023empirical,\n", + " title={An Empirical Study on Challenging Math Problem Solving with GPT-4},\n", + " author={Yiran Wu and Feiran Jia and Shaokun Zhang and Hangyu Li and Erkang Zhu and Yue Wang and Yin Tat Lee and Richard Peng and Qingyun Wu and Chi Wang},\n", + " year={2023},\n", + " booktitle={ArXiv preprint arXiv:2306.01337},\n", + "}\n", + "```\n", + "\n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33massistant\u001b[0m (to ragproxyagent):\n", + "\n", + "To use FLAML for a classification task and perform parallel training using Spark and train for 30 seconds while forcing cancel jobs if the time limit is reached, you can use the following code:\n", + "\n", + "```python\n", + "import flaml\n", + "from flaml.automl.spark.utils import to_pandas_on_spark\n", + "from pyspark.ml.feature import VectorAssembler\n", + "\n", + "# load your classification dataset as a pandas DataFrame\n", + "dataframe = ...\n", + "\n", + "# convert the pandas DataFrame to a pandas-on-spark DataFrame\n", + "psdf = to_pandas_on_spark(dataframe)\n", + "\n", + "# define the label column\n", + "label = ...\n", + "\n", + "# use VectorAssembler to merge all feature columns into a single vector column\n", + "columns = psdf.columns\n", + "feature_cols = [col for col in columns if col != label]\n", + "featurizer = VectorAssembler(inputCols=feature_cols, outputCol=\"features\")\n", + "psdf = featurizer.transform(psdf.to_spark(index_col=\"index\"))[\"index\", \"features\"]\n", + "\n", + "# configure the AutoML settings\n", + "settings = {\n", + " \"time_budget\": 30,\n", + " \"metric\": 'accuracy',\n", + " \"task\": 'classification',\n", + " \"log_file_name\": 'classification.log',\n", + " \"estimator_list\": ['lgbm_spark'],\n", + " \"n_concurrent_trials\": 2,\n", + " \"use_spark\": True,\n", + " \"force_cancel\": True\n", + "}\n", + "\n", + "# create and run the AutoML experiment\n", + "automl = flaml.AutoML()\n", + "automl.fit(\n", + " dataframe=psdf,\n", + " label=label,\n", + " **settings\n", + ")\n", + "```\n", + "\n", + "Note that you will need to replace the placeholders with your own dataset and label column names. This code will use FLAML's `lgbm_spark` estimator for training the classification model in parallel using Spark. The training will be restricted to 30 seconds, and if the time limit is reached, FLAML will force cancel the Spark jobs.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mragproxyagent\u001b[0m (to assistant):\n", + "\n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33massistant\u001b[0m (to ragproxyagent):\n", + "\n", + "UPDATE CONTEXT\n", + "\n", + "--------------------------------------------------------------------------------\n" + ] + } + ], + "source": [ + "# reset the assistant. Always reset the assistant before starting a new conversation.\n", + "assistant.reset()\n", + "\n", + "# given a problem, we use the ragproxyagent to generate a prompt to be sent to the assistant as the initial message.\n", + "# the assistant receives the message and generates a response. The response will be sent back to the ragproxyagent for processing.\n", + "# The conversation continues until the termination condition is met, in RetrieveChat, the termination condition when no human-in-loop is no code block detected.\n", + "# With human-in-loop, the conversation will continue until the user says \"exit\".\n", + "code_problem = \"How can I use FLAML to perform a classification task and use spark to do parallel training. Train for 30 seconds and force cancel jobs if time limit is reached.\"\n", + "chat_result = ragproxyagent.initiate_chat(\n", + " assistant, message=ragproxyagent.message_generator, problem=code_problem, search_string=\"spark\"\n", + ")" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Example 2\n", + "\n", + "[Back to top](#table-of-contents)\n", + "\n", + "Use RetrieveChat to answer a question that is not related to code generation.\n", + "\n", + "Problem: Who is the author of FLAML?" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "VectorDB returns doc_ids: [['7968cf3c', 'bdfbc921']]\n", + "\u001b[32mAdding content of doc 7968cf3c to context.\u001b[0m\n", + "\u001b[32mAdding content of doc bdfbc921 to context.\u001b[0m\n", + "\u001b[33mragproxyagent\u001b[0m (to assistant):\n", + "\n", + "You're a retrieve augmented coding assistant. You answer user's questions based on your own knowledge and the\n", + "context provided by the user.\n", + "If you can't answer the question with or without the current context, you should reply exactly `UPDATE CONTEXT`.\n", + "For code generation, you must obey the following rules:\n", + "Rule 1. You MUST NOT install any packages because all the packages needed are already installed.\n", + "Rule 2. You must follow the formats below to write your code:\n", + "```language\n", + "# your code\n", + "```\n", + "\n", + "User's question is: Who is the author of FLAML?\n", + "\n", + "Context is: # Research\n", + "\n", + "For technical details, please check our research publications.\n", + "\n", + "- [FLAML: A Fast and Lightweight AutoML Library](https://www.microsoft.com/en-us/research/publication/flaml-a-fast-and-lightweight-automl-library/). Chi Wang, Qingyun Wu, Markus Weimer, Erkang Zhu. MLSys 2021.\n", + "\n", + "```bibtex\n", + "@inproceedings{wang2021flaml,\n", + " title={FLAML: A Fast and Lightweight AutoML Library},\n", + " author={Chi Wang and Qingyun Wu and Markus Weimer and Erkang Zhu},\n", + " year={2021},\n", + " booktitle={MLSys},\n", + "}\n", + "```\n", + "\n", + "- [Frugal Optimization for Cost-related Hyperparameters](https://arxiv.org/abs/2005.01571). Qingyun Wu, Chi Wang, Silu Huang. AAAI 2021.\n", + "\n", + "```bibtex\n", + "@inproceedings{wu2021cfo,\n", + " title={Frugal Optimization for Cost-related Hyperparameters},\n", + " author={Qingyun Wu and Chi Wang and Silu Huang},\n", + " year={2021},\n", + " booktitle={AAAI},\n", + "}\n", + "```\n", + "\n", + "- [Economical Hyperparameter Optimization With Blended Search Strategy](https://www.microsoft.com/en-us/research/publication/economical-hyperparameter-optimization-with-blended-search-strategy/). Chi Wang, Qingyun Wu, Silu Huang, Amin Saied. ICLR 2021.\n", + "\n", + "```bibtex\n", + "@inproceedings{wang2021blendsearch,\n", + " title={Economical Hyperparameter Optimization With Blended Search Strategy},\n", + " author={Chi Wang and Qingyun Wu and Silu Huang and Amin Saied},\n", + " year={2021},\n", + " booktitle={ICLR},\n", + "}\n", + "```\n", + "\n", + "- [An Empirical Study on Hyperparameter Optimization for Fine-Tuning Pre-trained Language Models](https://aclanthology.org/2021.acl-long.178.pdf). Susan Xueqing Liu, Chi Wang. ACL 2021.\n", + "\n", + "```bibtex\n", + "@inproceedings{liuwang2021hpolm,\n", + " title={An Empirical Study on Hyperparameter Optimization for Fine-Tuning Pre-trained Language Models},\n", + " author={Susan Xueqing Liu and Chi Wang},\n", + " year={2021},\n", + " booktitle={ACL},\n", + "}\n", + "```\n", + "\n", + "- [ChaCha for Online AutoML](https://www.microsoft.com/en-us/research/publication/chacha-for-online-automl/). Qingyun Wu, Chi Wang, John Langford, Paul Mineiro and Marco Rossi. ICML 2021.\n", + "\n", + "```bibtex\n", + "@inproceedings{wu2021chacha,\n", + " title={ChaCha for Online AutoML},\n", + " author={Qingyun Wu and Chi Wang and John Langford and Paul Mineiro and Marco Rossi},\n", + " year={2021},\n", + " booktitle={ICML},\n", + "}\n", + "```\n", + "\n", + "- [Fair AutoML](https://arxiv.org/abs/2111.06495). Qingyun Wu, Chi Wang. ArXiv preprint arXiv:2111.06495 (2021).\n", + "\n", + "```bibtex\n", + "@inproceedings{wuwang2021fairautoml,\n", + " title={Fair AutoML},\n", + " author={Qingyun Wu and Chi Wang},\n", + " year={2021},\n", + " booktitle={ArXiv preprint arXiv:2111.06495},\n", + "}\n", + "```\n", + "\n", + "- [Mining Robust Default Configurations for Resource-constrained AutoML](https://arxiv.org/abs/2202.09927). Moe Kayali, Chi Wang. ArXiv preprint arXiv:2202.09927 (2022).\n", + "\n", + "```bibtex\n", + "@inproceedings{kayaliwang2022default,\n", + " title={Mining Robust Default Configurations for Resource-constrained AutoML},\n", + " author={Moe Kayali and Chi Wang},\n", + " year={2022},\n", + " booktitle={ArXiv preprint arXiv:2202.09927},\n", + "}\n", + "```\n", + "\n", + "- [Targeted Hyperparameter Optimization with Lexicographic Preferences Over Multiple Objectives](https://openreview.net/forum?id=0Ij9_q567Ma). Shaokun Zhang, Feiran Jia, Chi Wang, Qingyun Wu. ICLR 2023 (notable-top-5%).\n", + "\n", + "```bibtex\n", + "@inproceedings{zhang2023targeted,\n", + " title={Targeted Hyperparameter Optimization with Lexicographic Preferences Over Multiple Objectives},\n", + " author={Shaokun Zhang and Feiran Jia and Chi Wang and Qingyun Wu},\n", + " booktitle={International Conference on Learning Representations},\n", + " year={2023},\n", + " url={https://openreview.net/forum?id=0Ij9_q567Ma},\n", + "}\n", + "```\n", + "\n", + "- [Cost-Effective Hyperparameter Optimization for Large Language Model Generation Inference](https://arxiv.org/abs/2303.04673). Chi Wang, Susan Xueqing Liu, Ahmed H. Awadallah. ArXiv preprint arXiv:2303.04673 (2023).\n", + "\n", + "```bibtex\n", + "@inproceedings{wang2023EcoOptiGen,\n", + " title={Cost-Effective Hyperparameter Optimization for Large Language Model Generation Inference},\n", + " author={Chi Wang and Susan Xueqing Liu and Ahmed H. Awadallah},\n", + " year={2023},\n", + " booktitle={ArXiv preprint arXiv:2303.04673},\n", + "}\n", + "```\n", + "\n", + "- [An Empirical Study on Challenging Math Problem Solving with GPT-4](https://arxiv.org/abs/2306.01337). Yiran Wu, Feiran Jia, Shaokun Zhang, Hangyu Li, Erkang Zhu, Yue Wang, Yin Tat Lee, Richard Peng, Qingyun Wu, Chi Wang. ArXiv preprint arXiv:2306.01337 (2023).\n", + "\n", + "```bibtex\n", + "@inproceedings{wu2023empirical,\n", + " title={An Empirical Study on Challenging Math Problem Solving with GPT-4},\n", + " author={Yiran Wu and Feiran Jia and Shaokun Zhang and Hangyu Li and Erkang Zhu and Yue Wang and Yin Tat Lee and Richard Peng and Qingyun Wu and Chi Wang},\n", + " year={2023},\n", + " booktitle={ArXiv preprint arXiv:2306.01337},\n", + "}\n", + "```\n", + "# Integrate - Spark\n", + "\n", + "FLAML has integrated Spark for distributed training. There are two main aspects of integration with Spark:\n", + "\n", + "- Use Spark ML estimators for AutoML.\n", + "- Use Spark to run training in parallel spark jobs.\n", + "\n", + "## Spark ML Estimators\n", + "\n", + "FLAML integrates estimators based on Spark ML models. These models are trained in parallel using Spark, so we called them Spark estimators. To use these models, you first need to organize your data in the required format.\n", + "\n", + "### Data\n", + "\n", + "For Spark estimators, AutoML only consumes Spark data. FLAML provides a convenient function `to_pandas_on_spark` in the `flaml.automl.spark.utils` module to convert your data into a pandas-on-spark (`pyspark.pandas`) dataframe/series, which Spark estimators require.\n", + "\n", + "This utility function takes data in the form of a `pandas.Dataframe` or `pyspark.sql.Dataframe` and converts it into a pandas-on-spark dataframe. It also takes `pandas.Series` or `pyspark.sql.Dataframe` and converts it into a [pandas-on-spark](https://spark.apache.org/docs/latest/api/python/user_guide/pandas_on_spark/index.html) series. If you pass in a `pyspark.pandas.Dataframe`, it will not make any changes.\n", + "\n", + "This function also accepts optional arguments `index_col` and `default_index_type`.\n", + "\n", + "- `index_col` is the column name to use as the index, default is None.\n", + "- `default_index_type` is the default index type, default is \"distributed-sequence\". More info about default index type could be found on Spark official [documentation](https://spark.apache.org/docs/latest/api/python/user_guide/pandas_on_spark/options.html#default-index-type)\n", + "\n", + "Here is an example code snippet for Spark Data:\n", + "\n", + "```python\n", + "import pandas as pd\n", + "from flaml.automl.spark.utils import to_pandas_on_spark\n", + "\n", + "# Creating a dictionary\n", + "data = {\n", + " \"Square_Feet\": [800, 1200, 1800, 1500, 850],\n", + " \"Age_Years\": [20, 15, 10, 7, 25],\n", + " \"Price\": [100000, 200000, 300000, 240000, 120000],\n", + "}\n", + "\n", + "# Creating a pandas DataFrame\n", + "dataframe = pd.DataFrame(data)\n", + "label = \"Price\"\n", + "\n", + "# Convert to pandas-on-spark dataframe\n", + "psdf = to_pandas_on_spark(dataframe)\n", + "```\n", + "\n", + "To use Spark ML models you need to format your data appropriately. Specifically, use [`VectorAssembler`](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.ml.feature.VectorAssembler.html) to merge all feature columns into a single vector column.\n", + "\n", + "Here is an example of how to use it:\n", + "\n", + "```python\n", + "from pyspark.ml.feature import VectorAssembler\n", + "\n", + "columns = psdf.columns\n", + "feature_cols = [col for col in columns if col != label]\n", + "featurizer = VectorAssembler(inputCols=feature_cols, outputCol=\"features\")\n", + "psdf = featurizer.transform(psdf.to_spark(index_col=\"index\"))[\"index\", \"features\"]\n", + "```\n", + "\n", + "Later in conducting the experiment, use your pandas-on-spark data like non-spark data and pass them using `X_train, y_train` or `dataframe, label`.\n", + "\n", + "### Estimators\n", + "\n", + "#### Model List\n", + "\n", + "- `lgbm_spark`: The class for fine-tuning Spark version LightGBM models, using [SynapseML](https://microsoft.github.io/SynapseML/docs/features/lightgbm/about/) API.\n", + "\n", + "#### Usage\n", + "\n", + "First, prepare your data in the required format as described in the previous section.\n", + "\n", + "By including the models you intend to try in the `estimators_list` argument to `flaml.automl`, FLAML will start trying configurations for these models. If your input is Spark data, FLAML will also use estimators with the `_spark` postfix by default, even if you haven't specified them.\n", + "\n", + "Here is an example code snippet using SparkML models in AutoML:\n", + "\n", + "```python\n", + "import flaml\n", + "\n", + "# prepare your data in pandas-on-spark format as we previously mentioned\n", + "\n", + "automl = flaml.AutoML()\n", + "settings = {\n", + " \"time_budget\": 30,\n", + " \"metric\": \"r2\",\n", + " \"estimator_list\": [\"lgbm_spark\"], # this setting is optional\n", + " \"task\": \"regression\",\n", + "}\n", + "\n", + "automl.fit(\n", + " dataframe=psdf,\n", + " label=label,\n", + " **settings,\n", + ")\n", + "```\n", + "\n", + "[Link to notebook](https://github.com/microsoft/FLAML/blob/main/notebook/automl_bankrupt_synapseml.ipynb) | [Open in colab](https://colab.research.google.com/github/microsoft/FLAML/blob/main/notebook/automl_bankrupt_synapseml.ipynb)\n", + "\n", + "## Parallel Spark Jobs\n", + "\n", + "You can activate Spark as the parallel backend during parallel tuning in both [AutoML](/docs/Use-Cases/Task-Oriented-AutoML#parallel-tuning) and [Hyperparameter Tuning](/docs/Use-Cases/Tune-User-Defined-Function#parallel-tuning), by setting the `use_spark` to `true`. FLAML will dispatch your job to the distributed Spark backend using [`joblib-spark`](https://github.com/joblib/joblib-spark).\n", + "\n", + "Please note that you should not set `use_spark` to `true` when applying AutoML and Tuning for Spark Data. This is because only SparkML models will be used for Spark Data in AutoML and Tuning. As SparkML models run in parallel, there is no need to distribute them with `use_spark` again.\n", + "\n", + "All the Spark-related arguments are stated below. These arguments are available in both Hyperparameter Tuning and AutoML:\n", + "\n", + "- `use_spark`: boolean, default=False | Whether to use spark to run the training in parallel spark jobs. This can be used to accelerate training on large models and large datasets, but will incur more overhead in time and thus slow down training in some cases. GPU training is not supported yet when use_spark is True. For Spark clusters, by default, we will launch one trial per executor. However, sometimes we want to launch more trials than the number of executors (e.g., local mode). In this case, we can set the environment variable `FLAML_MAX_CONCURRENT` to override the detected `num_executors`. The final number of concurrent trials will be the minimum of `n_concurrent_trials` and `num_executors`.\n", + "- `n_concurrent_trials`: int, default=1 | The number of concurrent trials. When n_concurrent_trials > 1, FLAML performes parallel tuning.\n", + "- `force_cancel`: boolean, default=False | Whether to forcely cancel Spark jobs if the search time exceeded the time budget. Spark jobs include parallel tuning jobs and Spark-based model training jobs.\n", + "\n", + "An example code snippet for using parallel Spark jobs:\n", + "\n", + "```python\n", + "import flaml\n", + "\n", + "automl_experiment = flaml.AutoML()\n", + "automl_settings = {\n", + " \"time_budget\": 30,\n", + " \"metric\": \"r2\",\n", + " \"task\": \"regression\",\n", + " \"n_concurrent_trials\": 2,\n", + " \"use_spark\": True,\n", + " \"force_cancel\": True, # Activating the force_cancel option can immediately halt Spark jobs once they exceed the allocated time_budget.\n", + "}\n", + "\n", + "automl.fit(\n", + " dataframe=dataframe,\n", + " label=label,\n", + " **automl_settings,\n", + ")\n", + "```\n", + "\n", + "[Link to notebook](https://github.com/microsoft/FLAML/blob/main/notebook/integrate_spark.ipynb) | [Open in colab](https://colab.research.google.com/github/microsoft/FLAML/blob/main/notebook/integrate_spark.ipynb)\n", + "\n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33massistant\u001b[0m (to ragproxyagent):\n", + "\n", + "The authors of FLAML are Chi Wang, Qingyun Wu, Markus Weimer, and Erkang Zhu.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[32mAdding content of doc bdfbc921 to context.\u001b[0m\n", + "\u001b[33mragproxyagent\u001b[0m (to assistant):\n", + "\n", + "You're a retrieve augmented coding assistant. You answer user's questions based on your own knowledge and the\n", + "context provided by the user.\n", + "If you can't answer the question with or without the current context, you should reply exactly `UPDATE CONTEXT`.\n", + "For code generation, you must obey the following rules:\n", + "Rule 1. You MUST NOT install any packages because all the packages needed are already installed.\n", + "Rule 2. You must follow the formats below to write your code:\n", + "```language\n", + "# your code\n", + "```\n", + "\n", + "User's question is: Who is the author of FLAML?\n", + "\n", + "Context is: # Research\n", + "\n", + "For technical details, please check our research publications.\n", + "\n", + "- [FLAML: A Fast and Lightweight AutoML Library](https://www.microsoft.com/en-us/research/publication/flaml-a-fast-and-lightweight-automl-library/). Chi Wang, Qingyun Wu, Markus Weimer, Erkang Zhu. MLSys 2021.\n", + "\n", + "```bibtex\n", + "@inproceedings{wang2021flaml,\n", + " title={FLAML: A Fast and Lightweight AutoML Library},\n", + " author={Chi Wang and Qingyun Wu and Markus Weimer and Erkang Zhu},\n", + " year={2021},\n", + " booktitle={MLSys},\n", + "}\n", + "```\n", + "\n", + "- [Frugal Optimization for Cost-related Hyperparameters](https://arxiv.org/abs/2005.01571). Qingyun Wu, Chi Wang, Silu Huang. AAAI 2021.\n", + "\n", + "```bibtex\n", + "@inproceedings{wu2021cfo,\n", + " title={Frugal Optimization for Cost-related Hyperparameters},\n", + " author={Qingyun Wu and Chi Wang and Silu Huang},\n", + " year={2021},\n", + " booktitle={AAAI},\n", + "}\n", + "```\n", + "\n", + "- [Economical Hyperparameter Optimization With Blended Search Strategy](https://www.microsoft.com/en-us/research/publication/economical-hyperparameter-optimization-with-blended-search-strategy/). Chi Wang, Qingyun Wu, Silu Huang, Amin Saied. ICLR 2021.\n", + "\n", + "```bibtex\n", + "@inproceedings{wang2021blendsearch,\n", + " title={Economical Hyperparameter Optimization With Blended Search Strategy},\n", + " author={Chi Wang and Qingyun Wu and Silu Huang and Amin Saied},\n", + " year={2021},\n", + " booktitle={ICLR},\n", + "}\n", + "```\n", + "\n", + "- [An Empirical Study on Hyperparameter Optimization for Fine-Tuning Pre-trained Language Models](https://aclanthology.org/2021.acl-long.178.pdf). Susan Xueqing Liu, Chi Wang. ACL 2021.\n", + "\n", + "```bibtex\n", + "@inproceedings{liuwang2021hpolm,\n", + " title={An Empirical Study on Hyperparameter Optimization for Fine-Tuning Pre-trained Language Models},\n", + " author={Susan Xueqing Liu and Chi Wang},\n", + " year={2021},\n", + " booktitle={ACL},\n", + "}\n", + "```\n", + "\n", + "- [ChaCha for Online AutoML](https://www.microsoft.com/en-us/research/publication/chacha-for-online-automl/). Qingyun Wu, Chi Wang, John Langford, Paul Mineiro and Marco Rossi. ICML 2021.\n", + "\n", + "```bibtex\n", + "@inproceedings{wu2021chacha,\n", + " title={ChaCha for Online AutoML},\n", + " author={Qingyun Wu and Chi Wang and John Langford and Paul Mineiro and Marco Rossi},\n", + " year={2021},\n", + " booktitle={ICML},\n", + "}\n", + "```\n", + "\n", + "- [Fair AutoML](https://arxiv.org/abs/2111.06495). Qingyun Wu, Chi Wang. ArXiv preprint arXiv:2111.06495 (2021).\n", + "\n", + "```bibtex\n", + "@inproceedings{wuwang2021fairautoml,\n", + " title={Fair AutoML},\n", + " author={Qingyun Wu and Chi Wang},\n", + " year={2021},\n", + " booktitle={ArXiv preprint arXiv:2111.06495},\n", + "}\n", + "```\n", + "\n", + "- [Mining Robust Default Configurations for Resource-constrained AutoML](https://arxiv.org/abs/2202.09927). Moe Kayali, Chi Wang. ArXiv preprint arXiv:2202.09927 (2022).\n", + "\n", + "```bibtex\n", + "@inproceedings{kayaliwang2022default,\n", + " title={Mining Robust Default Configurations for Resource-constrained AutoML},\n", + " author={Moe Kayali and Chi Wang},\n", + " year={2022},\n", + " booktitle={ArXiv preprint arXiv:2202.09927},\n", + "}\n", + "```\n", + "\n", + "- [Targeted Hyperparameter Optimization with Lexicographic Preferences Over Multiple Objectives](https://openreview.net/forum?id=0Ij9_q567Ma). Shaokun Zhang, Feiran Jia, Chi Wang, Qingyun Wu. ICLR 2023 (notable-top-5%).\n", + "\n", + "```bibtex\n", + "@inproceedings{zhang2023targeted,\n", + " title={Targeted Hyperparameter Optimization with Lexicographic Preferences Over Multiple Objectives},\n", + " author={Shaokun Zhang and Feiran Jia and Chi Wang and Qingyun Wu},\n", + " booktitle={International Conference on Learning Representations},\n", + " year={2023},\n", + " url={https://openreview.net/forum?id=0Ij9_q567Ma},\n", + "}\n", + "```\n", + "\n", + "- [Cost-Effective Hyperparameter Optimization for Large Language Model Generation Inference](https://arxiv.org/abs/2303.04673). Chi Wang, Susan Xueqing Liu, Ahmed H. Awadallah. ArXiv preprint arXiv:2303.04673 (2023).\n", + "\n", + "```bibtex\n", + "@inproceedings{wang2023EcoOptiGen,\n", + " title={Cost-Effective Hyperparameter Optimization for Large Language Model Generation Inference},\n", + " author={Chi Wang and Susan Xueqing Liu and Ahmed H. Awadallah},\n", + " year={2023},\n", + " booktitle={ArXiv preprint arXiv:2303.04673},\n", + "}\n", + "```\n", + "\n", + "- [An Empirical Study on Challenging Math Problem Solving with GPT-4](https://arxiv.org/abs/2306.01337). Yiran Wu, Feiran Jia, Shaokun Zhang, Hangyu Li, Erkang Zhu, Yue Wang, Yin Tat Lee, Richard Peng, Qingyun Wu, Chi Wang. ArXiv preprint arXiv:2306.01337 (2023).\n", + "\n", + "```bibtex\n", + "@inproceedings{wu2023empirical,\n", + " title={An Empirical Study on Challenging Math Problem Solving with GPT-4},\n", + " author={Yiran Wu and Feiran Jia and Shaokun Zhang and Hangyu Li and Erkang Zhu and Yue Wang and Yin Tat Lee and Richard Peng and Qingyun Wu and Chi Wang},\n", + " year={2023},\n", + " booktitle={ArXiv preprint arXiv:2306.01337},\n", + "}\n", + "```\n", + "# Integrate - Spark\n", + "\n", + "FLAML has integrated Spark for distributed training. There are two main aspects of integration with Spark:\n", + "\n", + "- Use Spark ML estimators for AutoML.\n", + "- Use Spark to run training in parallel spark jobs.\n", + "\n", + "## Spark ML Estimators\n", + "\n", + "FLAML integrates estimators based on Spark ML models. These models are trained in parallel using Spark, so we called them Spark estimators. To use these models, you first need to organize your data in the required format.\n", + "\n", + "### Data\n", + "\n", + "For Spark estimators, AutoML only consumes Spark data. FLAML provides a convenient function `to_pandas_on_spark` in the `flaml.automl.spark.utils` module to convert your data into a pandas-on-spark (`pyspark.pandas`) dataframe/series, which Spark estimators require.\n", + "\n", + "This utility function takes data in the form of a `pandas.Dataframe` or `pyspark.sql.Dataframe` and converts it into a pandas-on-spark dataframe. It also takes `pandas.Series` or `pyspark.sql.Dataframe` and converts it into a [pandas-on-spark](https://spark.apache.org/docs/latest/api/python/user_guide/pandas_on_spark/index.html) series. If you pass in a `pyspark.pandas.Dataframe`, it will not make any changes.\n", + "\n", + "This function also accepts optional arguments `index_col` and `default_index_type`.\n", + "\n", + "- `index_col` is the column name to use as the index, default is None.\n", + "- `default_index_type` is the default index type, default is \"distributed-sequence\". More info about default index type could be found on Spark official [documentation](https://spark.apache.org/docs/latest/api/python/user_guide/pandas_on_spark/options.html#default-index-type)\n", + "\n", + "Here is an example code snippet for Spark Data:\n", + "\n", + "```python\n", + "import pandas as pd\n", + "from flaml.automl.spark.utils import to_pandas_on_spark\n", + "\n", + "# Creating a dictionary\n", + "data = {\n", + " \"Square_Feet\": [800, 1200, 1800, 1500, 850],\n", + " \"Age_Years\": [20, 15, 10, 7, 25],\n", + " \"Price\": [100000, 200000, 300000, 240000, 120000],\n", + "}\n", + "\n", + "# Creating a pandas DataFrame\n", + "dataframe = pd.DataFrame(data)\n", + "label = \"Price\"\n", + "\n", + "# Convert to pandas-on-spark dataframe\n", + "psdf = to_pandas_on_spark(dataframe)\n", + "```\n", + "\n", + "To use Spark ML models you need to format your data appropriately. Specifically, use [`VectorAssembler`](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.ml.feature.VectorAssembler.html) to merge all feature columns into a single vector column.\n", + "\n", + "Here is an example of how to use it:\n", + "\n", + "```python\n", + "from pyspark.ml.feature import VectorAssembler\n", + "\n", + "columns = psdf.columns\n", + "feature_cols = [col for col in columns if col != label]\n", + "featurizer = VectorAssembler(inputCols=feature_cols, outputCol=\"features\")\n", + "psdf = featurizer.transform(psdf.to_spark(index_col=\"index\"))[\"index\", \"features\"]\n", + "```\n", + "\n", + "Later in conducting the experiment, use your pandas-on-spark data like non-spark data and pass them using `X_train, y_train` or `dataframe, label`.\n", + "\n", + "### Estimators\n", + "\n", + "#### Model List\n", + "\n", + "- `lgbm_spark`: The class for fine-tuning Spark version LightGBM models, using [SynapseML](https://microsoft.github.io/SynapseML/docs/features/lightgbm/about/) API.\n", + "\n", + "#### Usage\n", + "\n", + "First, prepare your data in the required format as described in the previous section.\n", + "\n", + "By including the models you intend to try in the `estimators_list` argument to `flaml.automl`, FLAML will start trying configurations for these models. If your input is Spark data, FLAML will also use estimators with the `_spark` postfix by default, even if you haven't specified them.\n", + "\n", + "Here is an example code snippet using SparkML models in AutoML:\n", + "\n", + "```python\n", + "import flaml\n", + "\n", + "# prepare your data in pandas-on-spark format as we previously mentioned\n", + "\n", + "automl = flaml.AutoML()\n", + "settings = {\n", + " \"time_budget\": 30,\n", + " \"metric\": \"r2\",\n", + " \"estimator_list\": [\"lgbm_spark\"], # this setting is optional\n", + " \"task\": \"regression\",\n", + "}\n", + "\n", + "automl.fit(\n", + " dataframe=psdf,\n", + " label=label,\n", + " **settings,\n", + ")\n", + "```\n", + "\n", + "[Link to notebook](https://github.com/microsoft/FLAML/blob/main/notebook/automl_bankrupt_synapseml.ipynb) | [Open in colab](https://colab.research.google.com/github/microsoft/FLAML/blob/main/notebook/automl_bankrupt_synapseml.ipynb)\n", + "\n", + "## Parallel Spark Jobs\n", + "\n", + "You can activate Spark as the parallel backend during parallel tuning in both [AutoML](/docs/Use-Cases/Task-Oriented-AutoML#parallel-tuning) and [Hyperparameter Tuning](/docs/Use-Cases/Tune-User-Defined-Function#parallel-tuning), by setting the `use_spark` to `true`. FLAML will dispatch your job to the distributed Spark backend using [`joblib-spark`](https://github.com/joblib/joblib-spark).\n", + "\n", + "Please note that you should not set `use_spark` to `true` when applying AutoML and Tuning for Spark Data. This is because only SparkML models will be used for Spark Data in AutoML and Tuning. As SparkML models run in parallel, there is no need to distribute them with `use_spark` again.\n", + "\n", + "All the Spark-related arguments are stated below. These arguments are available in both Hyperparameter Tuning and AutoML:\n", + "\n", + "- `use_spark`: boolean, default=False | Whether to use spark to run the training in parallel spark jobs. This can be used to accelerate training on large models and large datasets, but will incur more overhead in time and thus slow down training in some cases. GPU training is not supported yet when use_spark is True. For Spark clusters, by default, we will launch one trial per executor. However, sometimes we want to launch more trials than the number of executors (e.g., local mode). In this case, we can set the environment variable `FLAML_MAX_CONCURRENT` to override the detected `num_executors`. The final number of concurrent trials will be the minimum of `n_concurrent_trials` and `num_executors`.\n", + "- `n_concurrent_trials`: int, default=1 | The number of concurrent trials. When n_concurrent_trials > 1, FLAML performes parallel tuning.\n", + "- `force_cancel`: boolean, default=False | Whether to forcely cancel Spark jobs if the search time exceeded the time budget. Spark jobs include parallel tuning jobs and Spark-based model training jobs.\n", + "\n", + "An example code snippet for using parallel Spark jobs:\n", + "\n", + "```python\n", + "import flaml\n", + "\n", + "automl_experiment = flaml.AutoML()\n", + "automl_settings = {\n", + " \"time_budget\": 30,\n", + " \"metric\": \"r2\",\n", + " \"task\": \"regression\",\n", + " \"n_concurrent_trials\": 2,\n", + " \"use_spark\": True,\n", + " \"force_cancel\": True, # Activating the force_cancel option can immediately halt Spark jobs once they exceed the allocated time_budget.\n", + "}\n", + "\n", + "automl.fit(\n", + " dataframe=dataframe,\n", + " label=label,\n", + " **automl_settings,\n", + ")\n", + "```\n", + "\n", + "[Link to notebook](https://github.com/microsoft/FLAML/blob/main/notebook/integrate_spark.ipynb) | [Open in colab](https://colab.research.google.com/github/microsoft/FLAML/blob/main/notebook/integrate_spark.ipynb)\n", + "\n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33massistant\u001b[0m (to ragproxyagent):\n", + "\n", + "The authors of FLAML are Chi Wang, Qingyun Wu, Markus Weimer, and Erkang Zhu.\n", + "\n", + "--------------------------------------------------------------------------------\n" + ] + } + ], + "source": [ + "# reset the assistant. Always reset the assistant before starting a new conversation.\n", + "assistant.reset()\n", + "\n", + "qa_problem = \"Who is the author of FLAML?\"\n", + "chat_result = ragproxyagent.initiate_chat(assistant, message=ragproxyagent.message_generator, problem=qa_problem)" + ] + } + ], + "metadata": { + "front_matter": { + "description": "Explore the use of AutoGen's RetrieveChat for tasks like code generation from docstrings, answering complex questions with human feedback, and exploiting features like Update Context, custom prompts, and few-shot learning.", + "tags": [ + "RAG" + ] + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.13" + }, + "skip_test": "Requires interactive usage" + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/notebook/agentchat_qdrant_RetrieveChat.ipynb b/notebook/agentchat_qdrant_RetrieveChat.ipynb index 4a040a5f49a..10d6b55e11c 100644 --- a/notebook/agentchat_qdrant_RetrieveChat.ipynb +++ b/notebook/agentchat_qdrant_RetrieveChat.ipynb @@ -21,7 +21,7 @@ "Some extra dependencies are needed for this notebook, which can be installed via pip:\n", "\n", "```bash\n", - "pip install \"pyautogen[retrievechat]>=0.2.3\" \"flaml[automl]\" \"qdrant_client[fastembed]\"\n", + "pip install \"pyautogen[retrievechat-qdrant]\" \"flaml[automl]\"\n", "```\n", "\n", "For more information, please refer to the [installation guide](/docs/installation/).\n", @@ -31,164 +31,172 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Requirement already satisfied: pyautogen>=0.2.3 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from pyautogen[retrievechat]>=0.2.3) (0.2.3)\n", - "Requirement already satisfied: flaml[automl] in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (2.1.1)\n", - "Requirement already satisfied: qdrant_client[fastembed] in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (1.7.0)\n", - "Requirement already satisfied: diskcache in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from pyautogen>=0.2.3->pyautogen[retrievechat]>=0.2.3) (5.6.3)\n", - "Requirement already satisfied: openai>=1.3 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from pyautogen>=0.2.3->pyautogen[retrievechat]>=0.2.3) (1.6.1)\n", - "Requirement already satisfied: pydantic<3,>=1.10 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from pyautogen>=0.2.3->pyautogen[retrievechat]>=0.2.3) (2.5.3)\n", - "Requirement already satisfied: python-dotenv in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from pyautogen>=0.2.3->pyautogen[retrievechat]>=0.2.3) (1.0.0)\n", - "Requirement already satisfied: termcolor in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from pyautogen>=0.2.3->pyautogen[retrievechat]>=0.2.3) (2.4.0)\n", - "Requirement already satisfied: tiktoken in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from pyautogen>=0.2.3->pyautogen[retrievechat]>=0.2.3) (0.5.2)\n", - "Requirement already satisfied: NumPy>=1.17.0rc1 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from flaml[automl]) (1.26.2)\n", - "Requirement already satisfied: lightgbm>=2.3.1 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from flaml[automl]) (4.2.0)\n", - "Requirement already satisfied: xgboost>=0.90 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from flaml[automl]) (2.0.3)\n", - "Requirement already satisfied: scipy>=1.4.1 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from flaml[automl]) (1.11.4)\n", - "Requirement already satisfied: pandas>=1.1.4 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from flaml[automl]) (2.1.4)\n", - "Requirement already satisfied: scikit-learn>=0.24 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from flaml[automl]) (1.3.2)\n", - "Requirement already satisfied: fastembed==0.1.1 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from qdrant_client[fastembed]) (0.1.1)\n", - "Requirement already satisfied: grpcio>=1.41.0 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from qdrant_client[fastembed]) (1.60.0)\n", - "Requirement already satisfied: grpcio-tools>=1.41.0 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from qdrant_client[fastembed]) (1.60.0)\n", - "Requirement already satisfied: httpx>=0.14.0 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from httpx[http2]>=0.14.0->qdrant_client[fastembed]) (0.26.0)\n", - "Requirement already satisfied: portalocker<3.0.0,>=2.7.0 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from qdrant_client[fastembed]) (2.8.2)\n", - "Requirement already satisfied: urllib3<2.0.0,>=1.26.14 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from qdrant_client[fastembed]) (1.26.18)\n", - "Requirement already satisfied: onnx<2.0,>=1.11 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from fastembed==0.1.1->qdrant_client[fastembed]) (1.15.0)\n", - "Requirement already satisfied: onnxruntime<2.0,>=1.15 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from fastembed==0.1.1->qdrant_client[fastembed]) (1.16.3)\n", - "Requirement already satisfied: requests<3.0,>=2.31 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from fastembed==0.1.1->qdrant_client[fastembed]) (2.31.0)\n", - "Requirement already satisfied: tokenizers<0.14,>=0.13 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from fastembed==0.1.1->qdrant_client[fastembed]) (0.13.3)\n", - "Requirement already satisfied: tqdm<5.0,>=4.65 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from fastembed==0.1.1->qdrant_client[fastembed]) (4.66.1)\n", - "Requirement already satisfied: chromadb in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from pyautogen[retrievechat]>=0.2.3) (0.4.21)\n", - "Requirement already satisfied: ipython in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from pyautogen[retrievechat]>=0.2.3) (8.19.0)\n", - "Requirement already satisfied: pypdf in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from pyautogen[retrievechat]>=0.2.3) (3.17.4)\n", - "Requirement already satisfied: sentence-transformers in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from pyautogen[retrievechat]>=0.2.3) (2.2.2)\n", - "Requirement already satisfied: protobuf<5.0dev,>=4.21.6 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from grpcio-tools>=1.41.0->qdrant_client[fastembed]) (4.25.1)\n", - "Requirement already satisfied: setuptools in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from grpcio-tools>=1.41.0->qdrant_client[fastembed]) (65.5.0)\n", - "Requirement already satisfied: anyio in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from httpx>=0.14.0->httpx[http2]>=0.14.0->qdrant_client[fastembed]) (4.2.0)\n", - "Requirement already satisfied: certifi in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from httpx>=0.14.0->httpx[http2]>=0.14.0->qdrant_client[fastembed]) (2023.11.17)\n", - "Requirement already satisfied: httpcore==1.* in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from httpx>=0.14.0->httpx[http2]>=0.14.0->qdrant_client[fastembed]) (1.0.2)\n", - "Requirement already satisfied: idna in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from httpx>=0.14.0->httpx[http2]>=0.14.0->qdrant_client[fastembed]) (3.6)\n", - "Requirement already satisfied: sniffio in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from httpx>=0.14.0->httpx[http2]>=0.14.0->qdrant_client[fastembed]) (1.3.0)\n", - "Requirement already satisfied: h11<0.15,>=0.13 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from httpcore==1.*->httpx>=0.14.0->httpx[http2]>=0.14.0->qdrant_client[fastembed]) (0.14.0)\n", - "Requirement already satisfied: h2<5,>=3 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from httpx[http2]>=0.14.0->qdrant_client[fastembed]) (4.1.0)\n", - "Requirement already satisfied: distro<2,>=1.7.0 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from openai>=1.3->pyautogen>=0.2.3->pyautogen[retrievechat]>=0.2.3) (1.9.0)\n", - "Requirement already satisfied: typing-extensions<5,>=4.7 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from openai>=1.3->pyautogen>=0.2.3->pyautogen[retrievechat]>=0.2.3) (4.9.0)\n", - "Requirement already satisfied: python-dateutil>=2.8.2 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from pandas>=1.1.4->flaml[automl]) (2.8.2)\n", - "Requirement already satisfied: pytz>=2020.1 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from pandas>=1.1.4->flaml[automl]) (2023.3.post1)\n", - "Requirement already satisfied: tzdata>=2022.1 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from pandas>=1.1.4->flaml[automl]) (2023.4)\n", - "Requirement already satisfied: annotated-types>=0.4.0 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from pydantic<3,>=1.10->pyautogen>=0.2.3->pyautogen[retrievechat]>=0.2.3) (0.6.0)\n", - "Requirement already satisfied: pydantic-core==2.14.6 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from pydantic<3,>=1.10->pyautogen>=0.2.3->pyautogen[retrievechat]>=0.2.3) (2.14.6)\n", - "Requirement already satisfied: joblib>=1.1.1 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from scikit-learn>=0.24->flaml[automl]) (1.3.2)\n", - "Requirement already satisfied: threadpoolctl>=2.0.0 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from scikit-learn>=0.24->flaml[automl]) (3.2.0)\n", - "Requirement already satisfied: chroma-hnswlib==0.7.3 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from chromadb->pyautogen[retrievechat]>=0.2.3) (0.7.3)\n", - "Requirement already satisfied: fastapi>=0.95.2 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from chromadb->pyautogen[retrievechat]>=0.2.3) (0.108.0)\n", - "Requirement already satisfied: uvicorn>=0.18.3 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from uvicorn[standard]>=0.18.3->chromadb->pyautogen[retrievechat]>=0.2.3) (0.25.0)\n", - "Requirement already satisfied: posthog>=2.4.0 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from chromadb->pyautogen[retrievechat]>=0.2.3) (3.1.0)\n", - "Requirement already satisfied: pulsar-client>=3.1.0 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from chromadb->pyautogen[retrievechat]>=0.2.3) (3.3.0)\n", - "Requirement already satisfied: opentelemetry-api>=1.2.0 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from chromadb->pyautogen[retrievechat]>=0.2.3) (1.22.0)\n", - "Requirement already satisfied: opentelemetry-exporter-otlp-proto-grpc>=1.2.0 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from chromadb->pyautogen[retrievechat]>=0.2.3) (1.22.0)\n", - "Requirement already satisfied: opentelemetry-instrumentation-fastapi>=0.41b0 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from chromadb->pyautogen[retrievechat]>=0.2.3) (0.43b0)\n", - "Requirement already satisfied: opentelemetry-sdk>=1.2.0 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from chromadb->pyautogen[retrievechat]>=0.2.3) (1.22.0)\n", - "Requirement already satisfied: pypika>=0.48.9 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from chromadb->pyautogen[retrievechat]>=0.2.3) (0.48.9)\n", - "Requirement already satisfied: overrides>=7.3.1 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from chromadb->pyautogen[retrievechat]>=0.2.3) (7.4.0)\n", - "Requirement already satisfied: importlib-resources in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from chromadb->pyautogen[retrievechat]>=0.2.3) (6.1.1)\n", - "Requirement already satisfied: bcrypt>=4.0.1 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from chromadb->pyautogen[retrievechat]>=0.2.3) (4.1.2)\n", - "Requirement already satisfied: typer>=0.9.0 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from chromadb->pyautogen[retrievechat]>=0.2.3) (0.9.0)\n", - "Requirement already satisfied: kubernetes>=28.1.0 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from chromadb->pyautogen[retrievechat]>=0.2.3) (28.1.0)\n", - "Requirement already satisfied: tenacity>=8.2.3 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from chromadb->pyautogen[retrievechat]>=0.2.3) (8.2.3)\n", - "Requirement already satisfied: PyYAML>=6.0.0 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from chromadb->pyautogen[retrievechat]>=0.2.3) (6.0.1)\n", - "Requirement already satisfied: mmh3>=4.0.1 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from chromadb->pyautogen[retrievechat]>=0.2.3) (4.0.1)\n", - "Requirement already satisfied: decorator in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from ipython->pyautogen[retrievechat]>=0.2.3) (5.1.1)\n", - "Requirement already satisfied: jedi>=0.16 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from ipython->pyautogen[retrievechat]>=0.2.3) (0.19.1)\n", - "Requirement already satisfied: matplotlib-inline in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from ipython->pyautogen[retrievechat]>=0.2.3) (0.1.6)\n", - "Requirement already satisfied: prompt-toolkit<3.1.0,>=3.0.41 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from ipython->pyautogen[retrievechat]>=0.2.3) (3.0.43)\n", - "Requirement already satisfied: pygments>=2.4.0 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from ipython->pyautogen[retrievechat]>=0.2.3) (2.17.2)\n", - "Requirement already satisfied: stack-data in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from ipython->pyautogen[retrievechat]>=0.2.3) (0.6.3)\n", - "Requirement already satisfied: traitlets>=5 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from ipython->pyautogen[retrievechat]>=0.2.3) (5.14.1)\n", - "Requirement already satisfied: pexpect>4.3 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from ipython->pyautogen[retrievechat]>=0.2.3) (4.9.0)\n", - "Requirement already satisfied: transformers<5.0.0,>=4.6.0 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from sentence-transformers->pyautogen[retrievechat]>=0.2.3) (4.33.3)\n", - "Requirement already satisfied: torch>=1.6.0 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from sentence-transformers->pyautogen[retrievechat]>=0.2.3) (2.1.2)\n", - "Requirement already satisfied: torchvision in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from sentence-transformers->pyautogen[retrievechat]>=0.2.3) (0.16.2)\n", - "Requirement already satisfied: nltk in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from sentence-transformers->pyautogen[retrievechat]>=0.2.3) (3.8.1)\n", - "Requirement already satisfied: sentencepiece in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from sentence-transformers->pyautogen[retrievechat]>=0.2.3) (0.1.99)\n", - "Requirement already satisfied: huggingface-hub>=0.4.0 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from sentence-transformers->pyautogen[retrievechat]>=0.2.3) (0.20.1)\n", - "Requirement already satisfied: regex>=2022.1.18 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from tiktoken->pyautogen>=0.2.3->pyautogen[retrievechat]>=0.2.3) (2023.12.25)\n", - "Requirement already satisfied: starlette<0.33.0,>=0.29.0 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from fastapi>=0.95.2->chromadb->pyautogen[retrievechat]>=0.2.3) (0.32.0.post1)\n", - "Requirement already satisfied: hyperframe<7,>=6.0 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from h2<5,>=3->httpx[http2]>=0.14.0->qdrant_client[fastembed]) (6.0.1)\n", - "Requirement already satisfied: hpack<5,>=4.0 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from h2<5,>=3->httpx[http2]>=0.14.0->qdrant_client[fastembed]) (4.0.0)\n", - "Requirement already satisfied: filelock in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from huggingface-hub>=0.4.0->sentence-transformers->pyautogen[retrievechat]>=0.2.3) (3.13.1)\n", - "Requirement already satisfied: fsspec>=2023.5.0 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from huggingface-hub>=0.4.0->sentence-transformers->pyautogen[retrievechat]>=0.2.3) (2023.12.2)\n", - "Requirement already satisfied: packaging>=20.9 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from huggingface-hub>=0.4.0->sentence-transformers->pyautogen[retrievechat]>=0.2.3) (23.2)\n", - "Requirement already satisfied: parso<0.9.0,>=0.8.3 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from jedi>=0.16->ipython->pyautogen[retrievechat]>=0.2.3) (0.8.3)\n", - "Requirement already satisfied: six>=1.9.0 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from kubernetes>=28.1.0->chromadb->pyautogen[retrievechat]>=0.2.3) (1.16.0)\n", - "Requirement already satisfied: google-auth>=1.0.1 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from kubernetes>=28.1.0->chromadb->pyautogen[retrievechat]>=0.2.3) (2.25.2)\n", - "Requirement already satisfied: websocket-client!=0.40.0,!=0.41.*,!=0.42.*,>=0.32.0 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from kubernetes>=28.1.0->chromadb->pyautogen[retrievechat]>=0.2.3) (1.7.0)\n", - "Requirement already satisfied: requests-oauthlib in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from kubernetes>=28.1.0->chromadb->pyautogen[retrievechat]>=0.2.3) (1.3.1)\n", - "Requirement already satisfied: oauthlib>=3.2.2 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from kubernetes>=28.1.0->chromadb->pyautogen[retrievechat]>=0.2.3) (3.2.2)\n", - "Requirement already satisfied: coloredlogs in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from onnxruntime<2.0,>=1.15->fastembed==0.1.1->qdrant_client[fastembed]) (15.0.1)\n", - "Requirement already satisfied: flatbuffers in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from onnxruntime<2.0,>=1.15->fastembed==0.1.1->qdrant_client[fastembed]) (23.5.26)\n", - "Requirement already satisfied: sympy in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from onnxruntime<2.0,>=1.15->fastembed==0.1.1->qdrant_client[fastembed]) (1.12)\n", - "Requirement already satisfied: deprecated>=1.2.6 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from opentelemetry-api>=1.2.0->chromadb->pyautogen[retrievechat]>=0.2.3) (1.2.14)\n", - "Requirement already satisfied: importlib-metadata<7.0,>=6.0 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from opentelemetry-api>=1.2.0->chromadb->pyautogen[retrievechat]>=0.2.3) (6.11.0)\n", - "Requirement already satisfied: backoff<3.0.0,>=1.10.0 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from opentelemetry-exporter-otlp-proto-grpc>=1.2.0->chromadb->pyautogen[retrievechat]>=0.2.3) (2.2.1)\n", - "Requirement already satisfied: googleapis-common-protos~=1.52 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from opentelemetry-exporter-otlp-proto-grpc>=1.2.0->chromadb->pyautogen[retrievechat]>=0.2.3) (1.62.0)\n", - "Requirement already satisfied: opentelemetry-exporter-otlp-proto-common==1.22.0 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from opentelemetry-exporter-otlp-proto-grpc>=1.2.0->chromadb->pyautogen[retrievechat]>=0.2.3) (1.22.0)\n", - "Requirement already satisfied: opentelemetry-proto==1.22.0 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from opentelemetry-exporter-otlp-proto-grpc>=1.2.0->chromadb->pyautogen[retrievechat]>=0.2.3) (1.22.0)\n", - "Requirement already satisfied: opentelemetry-instrumentation-asgi==0.43b0 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from opentelemetry-instrumentation-fastapi>=0.41b0->chromadb->pyautogen[retrievechat]>=0.2.3) (0.43b0)\n", - "Requirement already satisfied: opentelemetry-instrumentation==0.43b0 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from opentelemetry-instrumentation-fastapi>=0.41b0->chromadb->pyautogen[retrievechat]>=0.2.3) (0.43b0)\n", - "Requirement already satisfied: opentelemetry-semantic-conventions==0.43b0 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from opentelemetry-instrumentation-fastapi>=0.41b0->chromadb->pyautogen[retrievechat]>=0.2.3) (0.43b0)\n", - "Requirement already satisfied: opentelemetry-util-http==0.43b0 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from opentelemetry-instrumentation-fastapi>=0.41b0->chromadb->pyautogen[retrievechat]>=0.2.3) (0.43b0)\n", - "Requirement already satisfied: wrapt<2.0.0,>=1.0.0 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from opentelemetry-instrumentation==0.43b0->opentelemetry-instrumentation-fastapi>=0.41b0->chromadb->pyautogen[retrievechat]>=0.2.3) (1.16.0)\n", - "Requirement already satisfied: asgiref~=3.0 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from opentelemetry-instrumentation-asgi==0.43b0->opentelemetry-instrumentation-fastapi>=0.41b0->chromadb->pyautogen[retrievechat]>=0.2.3) (3.7.2)\n", - "Requirement already satisfied: ptyprocess>=0.5 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from pexpect>4.3->ipython->pyautogen[retrievechat]>=0.2.3) (0.7.0)\n", - "Requirement already satisfied: monotonic>=1.5 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from posthog>=2.4.0->chromadb->pyautogen[retrievechat]>=0.2.3) (1.6)\n", - "Requirement already satisfied: wcwidth in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from prompt-toolkit<3.1.0,>=3.0.41->ipython->pyautogen[retrievechat]>=0.2.3) (0.2.12)\n", - "Requirement already satisfied: charset-normalizer<4,>=2 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from requests<3.0,>=2.31->fastembed==0.1.1->qdrant_client[fastembed]) (3.3.2)\n", - "Requirement already satisfied: networkx in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from torch>=1.6.0->sentence-transformers->pyautogen[retrievechat]>=0.2.3) (3.2.1)\n", - "Requirement already satisfied: jinja2 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from torch>=1.6.0->sentence-transformers->pyautogen[retrievechat]>=0.2.3) (3.1.2)\n", - "Requirement already satisfied: nvidia-cuda-nvrtc-cu12==12.1.105 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from torch>=1.6.0->sentence-transformers->pyautogen[retrievechat]>=0.2.3) (12.1.105)\n", - "Requirement already satisfied: nvidia-cuda-runtime-cu12==12.1.105 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from torch>=1.6.0->sentence-transformers->pyautogen[retrievechat]>=0.2.3) (12.1.105)\n", - "Requirement already satisfied: nvidia-cuda-cupti-cu12==12.1.105 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from torch>=1.6.0->sentence-transformers->pyautogen[retrievechat]>=0.2.3) (12.1.105)\n", - "Requirement already satisfied: nvidia-cudnn-cu12==8.9.2.26 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from torch>=1.6.0->sentence-transformers->pyautogen[retrievechat]>=0.2.3) (8.9.2.26)\n", - "Requirement already satisfied: nvidia-cublas-cu12==12.1.3.1 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from torch>=1.6.0->sentence-transformers->pyautogen[retrievechat]>=0.2.3) (12.1.3.1)\n", - "Requirement already satisfied: nvidia-cufft-cu12==11.0.2.54 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from torch>=1.6.0->sentence-transformers->pyautogen[retrievechat]>=0.2.3) (11.0.2.54)\n", - "Requirement already satisfied: nvidia-curand-cu12==10.3.2.106 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from torch>=1.6.0->sentence-transformers->pyautogen[retrievechat]>=0.2.3) (10.3.2.106)\n", - "Requirement already satisfied: nvidia-cusolver-cu12==11.4.5.107 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from torch>=1.6.0->sentence-transformers->pyautogen[retrievechat]>=0.2.3) (11.4.5.107)\n", - "Requirement already satisfied: nvidia-cusparse-cu12==12.1.0.106 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from torch>=1.6.0->sentence-transformers->pyautogen[retrievechat]>=0.2.3) (12.1.0.106)\n", - "Requirement already satisfied: nvidia-nccl-cu12==2.18.1 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from torch>=1.6.0->sentence-transformers->pyautogen[retrievechat]>=0.2.3) (2.18.1)\n", - "Requirement already satisfied: nvidia-nvtx-cu12==12.1.105 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from torch>=1.6.0->sentence-transformers->pyautogen[retrievechat]>=0.2.3) (12.1.105)\n", - "Requirement already satisfied: triton==2.1.0 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from torch>=1.6.0->sentence-transformers->pyautogen[retrievechat]>=0.2.3) (2.1.0)\n", - "Requirement already satisfied: nvidia-nvjitlink-cu12 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from nvidia-cusolver-cu12==11.4.5.107->torch>=1.6.0->sentence-transformers->pyautogen[retrievechat]>=0.2.3) (12.3.101)\n", - "Requirement already satisfied: safetensors>=0.3.1 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from transformers<5.0.0,>=4.6.0->sentence-transformers->pyautogen[retrievechat]>=0.2.3) (0.4.1)\n", - "Requirement already satisfied: click<9.0.0,>=7.1.1 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from typer>=0.9.0->chromadb->pyautogen[retrievechat]>=0.2.3) (8.1.7)\n", - "Requirement already satisfied: httptools>=0.5.0 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from uvicorn[standard]>=0.18.3->chromadb->pyautogen[retrievechat]>=0.2.3) (0.6.1)\n", - "Requirement already satisfied: uvloop!=0.15.0,!=0.15.1,>=0.14.0 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from uvicorn[standard]>=0.18.3->chromadb->pyautogen[retrievechat]>=0.2.3) (0.19.0)\n", - "Requirement already satisfied: watchfiles>=0.13 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from uvicorn[standard]>=0.18.3->chromadb->pyautogen[retrievechat]>=0.2.3) (0.21.0)\n", - "Requirement already satisfied: websockets>=10.4 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from uvicorn[standard]>=0.18.3->chromadb->pyautogen[retrievechat]>=0.2.3) (12.0)\n", - "Requirement already satisfied: executing>=1.2.0 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from stack-data->ipython->pyautogen[retrievechat]>=0.2.3) (2.0.1)\n", - "Requirement already satisfied: asttokens>=2.1.0 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from stack-data->ipython->pyautogen[retrievechat]>=0.2.3) (2.4.1)\n", - "Requirement already satisfied: pure-eval in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from stack-data->ipython->pyautogen[retrievechat]>=0.2.3) (0.2.2)\n", - "Requirement already satisfied: pillow!=8.3.*,>=5.3.0 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from torchvision->sentence-transformers->pyautogen[retrievechat]>=0.2.3) (10.2.0)\n", - "Requirement already satisfied: cachetools<6.0,>=2.0.0 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from google-auth>=1.0.1->kubernetes>=28.1.0->chromadb->pyautogen[retrievechat]>=0.2.3) (5.3.2)\n", - "Requirement already satisfied: pyasn1-modules>=0.2.1 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from google-auth>=1.0.1->kubernetes>=28.1.0->chromadb->pyautogen[retrievechat]>=0.2.3) (0.3.0)\n", - "Requirement already satisfied: rsa<5,>=3.1.4 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from google-auth>=1.0.1->kubernetes>=28.1.0->chromadb->pyautogen[retrievechat]>=0.2.3) (4.9)\n", - "Requirement already satisfied: zipp>=0.5 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from importlib-metadata<7.0,>=6.0->opentelemetry-api>=1.2.0->chromadb->pyautogen[retrievechat]>=0.2.3) (3.17.0)\n", - "Requirement already satisfied: humanfriendly>=9.1 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from coloredlogs->onnxruntime<2.0,>=1.15->fastembed==0.1.1->qdrant_client[fastembed]) (10.0)\n", - "Requirement already satisfied: MarkupSafe>=2.0 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from jinja2->torch>=1.6.0->sentence-transformers->pyautogen[retrievechat]>=0.2.3) (2.1.3)\n", - "Requirement already satisfied: mpmath>=0.19 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from sympy->onnxruntime<2.0,>=1.15->fastembed==0.1.1->qdrant_client[fastembed]) (1.3.0)\n", - "Requirement already satisfied: pyasn1<0.6.0,>=0.4.6 in /workspaces/autogen/.venv-3.11/lib/python3.11/site-packages (from pyasn1-modules>=0.2.1->google-auth>=1.0.1->kubernetes>=28.1.0->chromadb->pyautogen[retrievechat]>=0.2.3) (0.5.1)\n", + "huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...\n", + "To disable this warning, you can either:\n", + "\t- Avoid using `tokenizers` before the fork if possible\n", + "\t- Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)\n", + "Requirement already satisfied: pyautogen>=0.2.3 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from pyautogen[retrievechat]>=0.2.3) (0.2.21)\n", + "Requirement already satisfied: flaml[automl] in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (2.1.2)\n", + "Requirement already satisfied: qdrant_client[fastembed] in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (1.6.4)\n", + "Requirement already satisfied: openai>=1.3 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from pyautogen>=0.2.3->pyautogen[retrievechat]>=0.2.3) (1.12.0)\n", + "Requirement already satisfied: diskcache in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from pyautogen>=0.2.3->pyautogen[retrievechat]>=0.2.3) (5.6.3)\n", + "Requirement already satisfied: termcolor in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from pyautogen>=0.2.3->pyautogen[retrievechat]>=0.2.3) (2.3.0)\n", + "Requirement already satisfied: numpy<2,>=1.17.0 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from pyautogen>=0.2.3->pyautogen[retrievechat]>=0.2.3) (1.26.4)\n", + "Requirement already satisfied: python-dotenv in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from pyautogen>=0.2.3->pyautogen[retrievechat]>=0.2.3) (1.0.0)\n", + "Requirement already satisfied: tiktoken in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from pyautogen>=0.2.3->pyautogen[retrievechat]>=0.2.3) (0.5.1)\n", + "Requirement already satisfied: pydantic!=2.6.0,<3,>=1.10 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from pyautogen>=0.2.3->pyautogen[retrievechat]>=0.2.3) (2.6.4)\n", + "Requirement already satisfied: docker in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from pyautogen>=0.2.3->pyautogen[retrievechat]>=0.2.3) (7.0.0)\n", + "Requirement already satisfied: lightgbm>=2.3.1 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from flaml[automl]) (4.1.0)\n", + "Requirement already satisfied: xgboost>=0.90 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from flaml[automl]) (2.0.1)\n", + "Requirement already satisfied: scipy>=1.4.1 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from flaml[automl]) (1.10.1)\n", + "Requirement already satisfied: pandas>=1.1.4 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from flaml[automl]) (2.2.0)\n", + "Requirement already satisfied: scikit-learn>=0.24 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from flaml[automl]) (1.3.2)\n", + "Requirement already satisfied: grpcio>=1.41.0 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from qdrant_client[fastembed]) (1.60.0)\n", + "Requirement already satisfied: grpcio-tools>=1.41.0 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from qdrant_client[fastembed]) (1.59.2)\n", + "Requirement already satisfied: httpx>=0.14.0 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from httpx[http2]>=0.14.0->qdrant_client[fastembed]) (0.25.1)\n", + "Requirement already satisfied: portalocker<3.0.0,>=2.7.0 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from qdrant_client[fastembed]) (2.8.2)\n", + "Requirement already satisfied: urllib3<2.0.0,>=1.26.14 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from qdrant_client[fastembed]) (1.26.18)\n", + "Requirement already satisfied: fastembed==0.1.1 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from qdrant_client[fastembed]) (0.1.1)\n", + "Requirement already satisfied: onnx<2.0,>=1.11 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from fastembed==0.1.1->qdrant_client[fastembed]) (1.15.0)\n", + "Requirement already satisfied: onnxruntime<2.0,>=1.15 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from fastembed==0.1.1->qdrant_client[fastembed]) (1.15.1)\n", + "Requirement already satisfied: requests<3.0,>=2.31 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from fastembed==0.1.1->qdrant_client[fastembed]) (2.31.0)\n", + "Requirement already satisfied: tokenizers<0.14,>=0.13 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from fastembed==0.1.1->qdrant_client[fastembed]) (0.13.3)\n", + "Requirement already satisfied: tqdm<5.0,>=4.65 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from fastembed==0.1.1->qdrant_client[fastembed]) (4.66.2)\n", + "Requirement already satisfied: chromadb in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from pyautogen[retrievechat]>=0.2.3) (0.4.22)\n", + "Requirement already satisfied: sentence-transformers in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from pyautogen[retrievechat]>=0.2.3) (2.3.1)\n", + "Requirement already satisfied: pypdf in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from pyautogen[retrievechat]>=0.2.3) (4.0.1)\n", + "Requirement already satisfied: ipython in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from pyautogen[retrievechat]>=0.2.3) (8.17.2)\n", + "Requirement already satisfied: protobuf<5.0dev,>=4.21.6 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from grpcio-tools>=1.41.0->qdrant_client[fastembed]) (4.23.4)\n", + "Requirement already satisfied: setuptools in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from grpcio-tools>=1.41.0->qdrant_client[fastembed]) (68.2.2)\n", + "Requirement already satisfied: anyio in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from httpx>=0.14.0->httpx[http2]>=0.14.0->qdrant_client[fastembed]) (3.7.1)\n", + "Requirement already satisfied: certifi in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from httpx>=0.14.0->httpx[http2]>=0.14.0->qdrant_client[fastembed]) (2024.2.2)\n", + "Requirement already satisfied: httpcore in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from httpx>=0.14.0->httpx[http2]>=0.14.0->qdrant_client[fastembed]) (1.0.1)\n", + "Requirement already satisfied: idna in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from httpx>=0.14.0->httpx[http2]>=0.14.0->qdrant_client[fastembed]) (3.6)\n", + "Requirement already satisfied: sniffio in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from httpx>=0.14.0->httpx[http2]>=0.14.0->qdrant_client[fastembed]) (1.3.0)\n", + "Requirement already satisfied: h2<5,>=3 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from httpx[http2]>=0.14.0->qdrant_client[fastembed]) (4.1.0)\n", + "Requirement already satisfied: distro<2,>=1.7.0 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from openai>=1.3->pyautogen>=0.2.3->pyautogen[retrievechat]>=0.2.3) (1.8.0)\n", + "Requirement already satisfied: typing-extensions<5,>=4.7 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from openai>=1.3->pyautogen>=0.2.3->pyautogen[retrievechat]>=0.2.3) (4.9.0)\n", + "Requirement already satisfied: python-dateutil>=2.8.2 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from pandas>=1.1.4->flaml[automl]) (2.8.2)\n", + "Requirement already satisfied: pytz>=2020.1 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from pandas>=1.1.4->flaml[automl]) (2024.1)\n", + "Requirement already satisfied: tzdata>=2022.7 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from pandas>=1.1.4->flaml[automl]) (2024.1)\n", + "Requirement already satisfied: annotated-types>=0.4.0 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from pydantic!=2.6.0,<3,>=1.10->pyautogen>=0.2.3->pyautogen[retrievechat]>=0.2.3) (0.6.0)\n", + "Requirement already satisfied: pydantic-core==2.16.3 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from pydantic!=2.6.0,<3,>=1.10->pyautogen>=0.2.3->pyautogen[retrievechat]>=0.2.3) (2.16.3)\n", + "Requirement already satisfied: joblib>=1.1.1 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from scikit-learn>=0.24->flaml[automl]) (1.3.2)\n", + "Requirement already satisfied: threadpoolctl>=2.0.0 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from scikit-learn>=0.24->flaml[automl]) (3.2.0)\n", + "Requirement already satisfied: build>=1.0.3 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from chromadb->pyautogen[retrievechat]>=0.2.3) (1.0.3)\n", + "Requirement already satisfied: chroma-hnswlib==0.7.3 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from chromadb->pyautogen[retrievechat]>=0.2.3) (0.7.3)\n", + "Requirement already satisfied: fastapi>=0.95.2 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from chromadb->pyautogen[retrievechat]>=0.2.3) (0.104.1)\n", + "Requirement already satisfied: uvicorn>=0.18.3 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from uvicorn[standard]>=0.18.3->chromadb->pyautogen[retrievechat]>=0.2.3) (0.24.0)\n", + "Requirement already satisfied: posthog>=2.4.0 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from chromadb->pyautogen[retrievechat]>=0.2.3) (3.0.2)\n", + "Requirement already satisfied: pulsar-client>=3.1.0 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from chromadb->pyautogen[retrievechat]>=0.2.3) (3.3.0)\n", + "Requirement already satisfied: opentelemetry-api>=1.2.0 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from chromadb->pyautogen[retrievechat]>=0.2.3) (1.20.0)\n", + "Requirement already satisfied: opentelemetry-exporter-otlp-proto-grpc>=1.2.0 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from chromadb->pyautogen[retrievechat]>=0.2.3) (1.20.0)\n", + "Requirement already satisfied: opentelemetry-instrumentation-fastapi>=0.41b0 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from chromadb->pyautogen[retrievechat]>=0.2.3) (0.41b0)\n", + "Requirement already satisfied: opentelemetry-sdk>=1.2.0 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from chromadb->pyautogen[retrievechat]>=0.2.3) (1.20.0)\n", + "Requirement already satisfied: pypika>=0.48.9 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from chromadb->pyautogen[retrievechat]>=0.2.3) (0.48.9)\n", + "Requirement already satisfied: overrides>=7.3.1 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from chromadb->pyautogen[retrievechat]>=0.2.3) (7.4.0)\n", + "Requirement already satisfied: importlib-resources in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from chromadb->pyautogen[retrievechat]>=0.2.3) (6.1.1)\n", + "Requirement already satisfied: bcrypt>=4.0.1 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from chromadb->pyautogen[retrievechat]>=0.2.3) (4.0.1)\n", + "Requirement already satisfied: typer>=0.9.0 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from chromadb->pyautogen[retrievechat]>=0.2.3) (0.9.0)\n", + "Requirement already satisfied: kubernetes>=28.1.0 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from chromadb->pyautogen[retrievechat]>=0.2.3) (28.1.0)\n", + "Requirement already satisfied: tenacity>=8.2.3 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from chromadb->pyautogen[retrievechat]>=0.2.3) (8.2.3)\n", + "Requirement already satisfied: PyYAML>=6.0.0 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from chromadb->pyautogen[retrievechat]>=0.2.3) (6.0.1)\n", + "Requirement already satisfied: mmh3>=4.0.1 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from chromadb->pyautogen[retrievechat]>=0.2.3) (4.0.1)\n", + "Requirement already satisfied: packaging>=14.0 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from docker->pyautogen>=0.2.3->pyautogen[retrievechat]>=0.2.3) (23.2)\n", + "Requirement already satisfied: decorator in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from ipython->pyautogen[retrievechat]>=0.2.3) (5.1.1)\n", + "Requirement already satisfied: jedi>=0.16 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from ipython->pyautogen[retrievechat]>=0.2.3) (0.19.1)\n", + "Requirement already satisfied: matplotlib-inline in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from ipython->pyautogen[retrievechat]>=0.2.3) (0.1.6)\n", + "Requirement already satisfied: prompt-toolkit!=3.0.37,<3.1.0,>=3.0.30 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from ipython->pyautogen[retrievechat]>=0.2.3) (3.0.39)\n", + "Requirement already satisfied: pygments>=2.4.0 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from ipython->pyautogen[retrievechat]>=0.2.3) (2.16.1)\n", + "Requirement already satisfied: stack-data in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from ipython->pyautogen[retrievechat]>=0.2.3) (0.6.3)\n", + "Requirement already satisfied: traitlets>=5 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from ipython->pyautogen[retrievechat]>=0.2.3) (5.14.2)\n", + "Requirement already satisfied: exceptiongroup in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from ipython->pyautogen[retrievechat]>=0.2.3) (1.1.3)\n", + "Requirement already satisfied: pexpect>4.3 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from ipython->pyautogen[retrievechat]>=0.2.3) (4.8.0)\n", + "Requirement already satisfied: transformers<5.0.0,>=4.32.0 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from sentence-transformers->pyautogen[retrievechat]>=0.2.3) (4.33.3)\n", + "Requirement already satisfied: torch>=1.11.0 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from sentence-transformers->pyautogen[retrievechat]>=0.2.3) (2.2.0)\n", + "Requirement already satisfied: nltk in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from sentence-transformers->pyautogen[retrievechat]>=0.2.3) (3.8.1)\n", + "Requirement already satisfied: sentencepiece in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from sentence-transformers->pyautogen[retrievechat]>=0.2.3) (0.1.99)\n", + "Requirement already satisfied: huggingface-hub>=0.15.1 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from sentence-transformers->pyautogen[retrievechat]>=0.2.3) (0.20.3)\n", + "Requirement already satisfied: Pillow in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from sentence-transformers->pyautogen[retrievechat]>=0.2.3) (10.2.0)\n", + "Requirement already satisfied: regex>=2022.1.18 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from tiktoken->pyautogen>=0.2.3->pyautogen[retrievechat]>=0.2.3) (2023.12.25)\n", + "Requirement already satisfied: pyproject_hooks in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from build>=1.0.3->chromadb->pyautogen[retrievechat]>=0.2.3) (1.0.0)\n", + "Requirement already satisfied: tomli>=1.1.0 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from build>=1.0.3->chromadb->pyautogen[retrievechat]>=0.2.3) (2.0.1)\n", + "Requirement already satisfied: starlette<0.28.0,>=0.27.0 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from fastapi>=0.95.2->chromadb->pyautogen[retrievechat]>=0.2.3) (0.27.0)\n", + "Requirement already satisfied: hyperframe<7,>=6.0 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from h2<5,>=3->httpx[http2]>=0.14.0->qdrant_client[fastembed]) (6.0.1)\n", + "Requirement already satisfied: hpack<5,>=4.0 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from h2<5,>=3->httpx[http2]>=0.14.0->qdrant_client[fastembed]) (4.0.0)\n", + "Requirement already satisfied: filelock in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from huggingface-hub>=0.15.1->sentence-transformers->pyautogen[retrievechat]>=0.2.3) (3.13.1)\n", + "Requirement already satisfied: fsspec>=2023.5.0 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from huggingface-hub>=0.15.1->sentence-transformers->pyautogen[retrievechat]>=0.2.3) (2024.2.0)\n", + "Requirement already satisfied: parso<0.9.0,>=0.8.3 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from jedi>=0.16->ipython->pyautogen[retrievechat]>=0.2.3) (0.8.3)\n", + "Requirement already satisfied: six>=1.9.0 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from kubernetes>=28.1.0->chromadb->pyautogen[retrievechat]>=0.2.3) (1.16.0)\n", + "Requirement already satisfied: google-auth>=1.0.1 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from kubernetes>=28.1.0->chromadb->pyautogen[retrievechat]>=0.2.3) (2.23.4)\n", + "Requirement already satisfied: websocket-client!=0.40.0,!=0.41.*,!=0.42.*,>=0.32.0 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from kubernetes>=28.1.0->chromadb->pyautogen[retrievechat]>=0.2.3) (1.6.4)\n", + "Requirement already satisfied: requests-oauthlib in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from kubernetes>=28.1.0->chromadb->pyautogen[retrievechat]>=0.2.3) (1.3.1)\n", + "Requirement already satisfied: oauthlib>=3.2.2 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from kubernetes>=28.1.0->chromadb->pyautogen[retrievechat]>=0.2.3) (3.2.2)\n", + "Requirement already satisfied: coloredlogs in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from onnxruntime<2.0,>=1.15->fastembed==0.1.1->qdrant_client[fastembed]) (15.0.1)\n", + "Requirement already satisfied: flatbuffers in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from onnxruntime<2.0,>=1.15->fastembed==0.1.1->qdrant_client[fastembed]) (23.5.26)\n", + "Requirement already satisfied: sympy in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from onnxruntime<2.0,>=1.15->fastembed==0.1.1->qdrant_client[fastembed]) (1.12)\n", + "Requirement already satisfied: deprecated>=1.2.6 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from opentelemetry-api>=1.2.0->chromadb->pyautogen[retrievechat]>=0.2.3) (1.2.14)\n", + "Requirement already satisfied: importlib-metadata<7.0,>=6.0 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from opentelemetry-api>=1.2.0->chromadb->pyautogen[retrievechat]>=0.2.3) (6.11.0)\n", + "Requirement already satisfied: backoff<3.0.0,>=1.10.0 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from opentelemetry-exporter-otlp-proto-grpc>=1.2.0->chromadb->pyautogen[retrievechat]>=0.2.3) (2.2.1)\n", + "Requirement already satisfied: googleapis-common-protos~=1.52 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from opentelemetry-exporter-otlp-proto-grpc>=1.2.0->chromadb->pyautogen[retrievechat]>=0.2.3) (1.61.0)\n", + "Requirement already satisfied: opentelemetry-exporter-otlp-proto-common==1.20.0 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from opentelemetry-exporter-otlp-proto-grpc>=1.2.0->chromadb->pyautogen[retrievechat]>=0.2.3) (1.20.0)\n", + "Requirement already satisfied: opentelemetry-proto==1.20.0 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from opentelemetry-exporter-otlp-proto-grpc>=1.2.0->chromadb->pyautogen[retrievechat]>=0.2.3) (1.20.0)\n", + "Requirement already satisfied: opentelemetry-instrumentation-asgi==0.41b0 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from opentelemetry-instrumentation-fastapi>=0.41b0->chromadb->pyautogen[retrievechat]>=0.2.3) (0.41b0)\n", + "Requirement already satisfied: opentelemetry-instrumentation==0.41b0 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from opentelemetry-instrumentation-fastapi>=0.41b0->chromadb->pyautogen[retrievechat]>=0.2.3) (0.41b0)\n", + "Requirement already satisfied: opentelemetry-semantic-conventions==0.41b0 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from opentelemetry-instrumentation-fastapi>=0.41b0->chromadb->pyautogen[retrievechat]>=0.2.3) (0.41b0)\n", + "Requirement already satisfied: opentelemetry-util-http==0.41b0 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from opentelemetry-instrumentation-fastapi>=0.41b0->chromadb->pyautogen[retrievechat]>=0.2.3) (0.41b0)\n", + "Requirement already satisfied: wrapt<2.0.0,>=1.0.0 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from opentelemetry-instrumentation==0.41b0->opentelemetry-instrumentation-fastapi>=0.41b0->chromadb->pyautogen[retrievechat]>=0.2.3) (1.16.0)\n", + "Requirement already satisfied: asgiref~=3.0 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from opentelemetry-instrumentation-asgi==0.41b0->opentelemetry-instrumentation-fastapi>=0.41b0->chromadb->pyautogen[retrievechat]>=0.2.3) (3.7.2)\n", + "Requirement already satisfied: ptyprocess>=0.5 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from pexpect>4.3->ipython->pyautogen[retrievechat]>=0.2.3) (0.7.0)\n", + "Requirement already satisfied: monotonic>=1.5 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from posthog>=2.4.0->chromadb->pyautogen[retrievechat]>=0.2.3) (1.6)\n", + "Requirement already satisfied: wcwidth in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from prompt-toolkit!=3.0.37,<3.1.0,>=3.0.30->ipython->pyautogen[retrievechat]>=0.2.3) (0.2.9)\n", + "Requirement already satisfied: charset-normalizer<4,>=2 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from requests<3.0,>=2.31->fastembed==0.1.1->qdrant_client[fastembed]) (3.3.2)\n", + "Requirement already satisfied: networkx in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from torch>=1.11.0->sentence-transformers->pyautogen[retrievechat]>=0.2.3) (3.2.1)\n", + "Requirement already satisfied: jinja2 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from torch>=1.11.0->sentence-transformers->pyautogen[retrievechat]>=0.2.3) (3.1.3)\n", + "Requirement already satisfied: nvidia-cuda-nvrtc-cu12==12.1.105 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from torch>=1.11.0->sentence-transformers->pyautogen[retrievechat]>=0.2.3) (12.1.105)\n", + "Requirement already satisfied: nvidia-cuda-runtime-cu12==12.1.105 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from torch>=1.11.0->sentence-transformers->pyautogen[retrievechat]>=0.2.3) (12.1.105)\n", + "Requirement already satisfied: nvidia-cuda-cupti-cu12==12.1.105 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from torch>=1.11.0->sentence-transformers->pyautogen[retrievechat]>=0.2.3) (12.1.105)\n", + "Requirement already satisfied: nvidia-cudnn-cu12==8.9.2.26 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from torch>=1.11.0->sentence-transformers->pyautogen[retrievechat]>=0.2.3) (8.9.2.26)\n", + "Requirement already satisfied: nvidia-cublas-cu12==12.1.3.1 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from torch>=1.11.0->sentence-transformers->pyautogen[retrievechat]>=0.2.3) (12.1.3.1)\n", + "Requirement already satisfied: nvidia-cufft-cu12==11.0.2.54 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from torch>=1.11.0->sentence-transformers->pyautogen[retrievechat]>=0.2.3) (11.0.2.54)\n", + "Requirement already satisfied: nvidia-curand-cu12==10.3.2.106 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from torch>=1.11.0->sentence-transformers->pyautogen[retrievechat]>=0.2.3) (10.3.2.106)\n", + "Requirement already satisfied: nvidia-cusolver-cu12==11.4.5.107 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from torch>=1.11.0->sentence-transformers->pyautogen[retrievechat]>=0.2.3) (11.4.5.107)\n", + "Requirement already satisfied: nvidia-cusparse-cu12==12.1.0.106 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from torch>=1.11.0->sentence-transformers->pyautogen[retrievechat]>=0.2.3) (12.1.0.106)\n", + "Requirement already satisfied: nvidia-nccl-cu12==2.19.3 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from torch>=1.11.0->sentence-transformers->pyautogen[retrievechat]>=0.2.3) (2.19.3)\n", + "Requirement already satisfied: nvidia-nvtx-cu12==12.1.105 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from torch>=1.11.0->sentence-transformers->pyautogen[retrievechat]>=0.2.3) (12.1.105)\n", + "Requirement already satisfied: triton==2.2.0 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from torch>=1.11.0->sentence-transformers->pyautogen[retrievechat]>=0.2.3) (2.2.0)\n", + "Requirement already satisfied: nvidia-nvjitlink-cu12 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from nvidia-cusolver-cu12==11.4.5.107->torch>=1.11.0->sentence-transformers->pyautogen[retrievechat]>=0.2.3) (12.3.52)\n", + "Requirement already satisfied: safetensors>=0.3.1 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from transformers<5.0.0,>=4.32.0->sentence-transformers->pyautogen[retrievechat]>=0.2.3) (0.3.2)\n", + "Requirement already satisfied: click<9.0.0,>=7.1.1 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from typer>=0.9.0->chromadb->pyautogen[retrievechat]>=0.2.3) (8.1.7)\n", + "Requirement already satisfied: h11>=0.8 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from uvicorn>=0.18.3->uvicorn[standard]>=0.18.3->chromadb->pyautogen[retrievechat]>=0.2.3) (0.14.0)\n", + "Requirement already satisfied: httptools>=0.5.0 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from uvicorn[standard]>=0.18.3->chromadb->pyautogen[retrievechat]>=0.2.3) (0.6.1)\n", + "Requirement already satisfied: uvloop!=0.15.0,!=0.15.1,>=0.14.0 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from uvicorn[standard]>=0.18.3->chromadb->pyautogen[retrievechat]>=0.2.3) (0.19.0)\n", + "Requirement already satisfied: watchfiles>=0.13 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from uvicorn[standard]>=0.18.3->chromadb->pyautogen[retrievechat]>=0.2.3) (0.21.0)\n", + "Requirement already satisfied: websockets>=10.4 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from uvicorn[standard]>=0.18.3->chromadb->pyautogen[retrievechat]>=0.2.3) (11.0.3)\n", + "Requirement already satisfied: executing>=1.2.0 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from stack-data->ipython->pyautogen[retrievechat]>=0.2.3) (2.0.1)\n", + "Requirement already satisfied: asttokens>=2.1.0 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from stack-data->ipython->pyautogen[retrievechat]>=0.2.3) (2.4.1)\n", + "Requirement already satisfied: pure-eval in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from stack-data->ipython->pyautogen[retrievechat]>=0.2.3) (0.2.2)\n", + "Requirement already satisfied: cachetools<6.0,>=2.0.0 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from google-auth>=1.0.1->kubernetes>=28.1.0->chromadb->pyautogen[retrievechat]>=0.2.3) (5.3.2)\n", + "Requirement already satisfied: pyasn1-modules>=0.2.1 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from google-auth>=1.0.1->kubernetes>=28.1.0->chromadb->pyautogen[retrievechat]>=0.2.3) (0.3.0)\n", + "Requirement already satisfied: rsa<5,>=3.1.4 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from google-auth>=1.0.1->kubernetes>=28.1.0->chromadb->pyautogen[retrievechat]>=0.2.3) (4.9)\n", + "Requirement already satisfied: zipp>=0.5 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from importlib-metadata<7.0,>=6.0->opentelemetry-api>=1.2.0->chromadb->pyautogen[retrievechat]>=0.2.3) (3.17.0)\n", + "Requirement already satisfied: humanfriendly>=9.1 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from coloredlogs->onnxruntime<2.0,>=1.15->fastembed==0.1.1->qdrant_client[fastembed]) (10.0)\n", + "Requirement already satisfied: MarkupSafe>=2.0 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from jinja2->torch>=1.11.0->sentence-transformers->pyautogen[retrievechat]>=0.2.3) (2.1.5)\n", + "Requirement already satisfied: mpmath>=0.19 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from sympy->onnxruntime<2.0,>=1.15->fastembed==0.1.1->qdrant_client[fastembed]) (1.3.0)\n", + "Requirement already satisfied: pyasn1<0.6.0,>=0.4.6 in /home/lijiang1/anaconda3/envs/autogen/lib/python3.10/site-packages (from pyasn1-modules>=0.2.1->google-auth>=1.0.1->kubernetes>=28.1.0->chromadb->pyautogen[retrievechat]>=0.2.3) (0.5.0)\n", "Note: you may need to restart the kernel to use updated packages.\n" ] } ], "source": [ - "%pip install \"pyautogen[retrievechat]>=0.2.3\" \"flaml[automl]\" \"qdrant_client[fastembed]\"" + "%pip install \"pyautogen[retrievechat-qdrant]\" \"flaml[automl]\"" ] }, { @@ -203,14 +211,14 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "models to use: ['gpt-4-1106-preview', 'gpt-4-turbo-preview', 'gpt-4-0613', 'gpt-35-turbo-0613', 'gpt-35-turbo-1106']\n" + "models to use: ['gpt4-1106-preview', 'gpt-35-turbo', 'gpt-35-turbo-0613']\n" ] } ], @@ -225,20 +233,7 @@ "# a vector database instance\n", "from autogen.retrieve_utils import TEXT_FORMATS\n", "\n", - "config_list = autogen.config_list_from_json(\n", - " env_or_file=\"OAI_CONFIG_LIST\",\n", - " file_location=\".\",\n", - " filter_dict={\n", - " \"model\": {\n", - " \"gpt-4\",\n", - " \"gpt4\",\n", - " \"gpt-4-32k\",\n", - " \"gpt-4-32k-0314\",\n", - " \"gpt-35-turbo\",\n", - " \"gpt-3.5-turbo\",\n", - " }\n", - " },\n", - ")\n", + "config_list = autogen.config_list_from_json(\"OAI_CONFIG_LIST\")\n", "\n", "assert len(config_list) > 0\n", "print(\"models to use: \", [config_list[i][\"model\"] for i in range(len(config_list))])" @@ -258,7 +253,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 9, "metadata": {}, "outputs": [ { @@ -266,7 +261,7 @@ "output_type": "stream", "text": [ "Accepted file formats for `docs_path`:\n", - "['txt', 'json', 'csv', 'tsv', 'md', 'html', 'htm', 'rtf', 'rst', 'jsonl', 'log', 'xml', 'yaml', 'yml', 'pdf']\n" + "['yml', 'ppt', 'org', 'doc', 'epub', 'rst', 'log', 'docx', 'htm', 'html', 'tsv', 'csv', 'json', 'yaml', 'xlsx', 'pptx', 'rtf', 'msg', 'odt', 'pdf', 'jsonl', 'md', 'xml', 'txt']\n" ] } ], @@ -289,7 +284,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 10, "metadata": {}, "outputs": [], "source": [ @@ -332,8 +327,7 @@ " \"client\": QdrantClient(\":memory:\"),\n", " \"embedding_model\": \"BAAI/bge-small-en-v1.5\",\n", " },\n", - " # code_execution_config={\n", - " # \"use_docker\": False,}\n", + " code_execution_config=False,\n", ")" ] }, @@ -354,17 +348,42 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Trying to create collection.\n", - "\u001b[32mAdding doc_id 0 to context.\u001b[0m\n", - "\u001b[32mAdding doc_id 2 to context.\u001b[0m\n", - "\u001b[32mAdding doc_id 1 to context.\u001b[0m\n", + "Trying to create collection.\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2024-04-07 18:30:12,489 - autogen.agentchat.contrib.qdrant_retrieve_user_proxy_agent - INFO - Found 3 chunks.\u001b[0m\n", + "Model gpt4-1106-preview not found. Using cl100k_base encoding.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[32mAdding content of doc 0 to context.\u001b[0m\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Model gpt4-1106-preview not found. Using cl100k_base encoding.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ "\u001b[33mragproxyagent\u001b[0m (to assistant):\n", "\n", "You're a retrieve augmented coding assistant. You answer user's questions based on your own knowledge and the\n", @@ -385,8 +404,8 @@ "![Python Version](https://img.shields.io/badge/3.8%20%7C%203.9%20%7C%203.10-blue)\n", "[![Downloads](https://pepy.tech/badge/flaml)](https://pepy.tech/project/flaml)\n", "[![](https://img.shields.io/discord/1025786666260111483?logo=discord&style=flat)](https://discord.gg/Cppx2vSPVP)\n", - "\n", "\n", + "\n", "\n", "# A Fast Library for Automated Machine Learning & Tuning\n", "\n", @@ -405,15 +424,15 @@ "\n", ":fire: FLAML supports Code-First AutoML & Tuning – Private Preview in [Microsoft Fabric Data Science](https://learn.microsoft.com/en-us/fabric/data-science/).\n", "\n", - "\n", "## What is FLAML\n", + "\n", "FLAML is a lightweight Python library for efficient automation of machine\n", "learning and AI operations. It automates workflow based on large language models, machine learning models, etc.\n", "and optimizes their performance.\n", "\n", - "* FLAML enables building next-gen GPT-X applications based on multi-agent conversations with minimal effort. It simplifies the orchestration, automation and optimization of a complex GPT-X workflow. It maximizes the performance of GPT-X models and augments their weakness.\n", - "* For common machine learning tasks like classification and regression, it quickly finds quality models for user-provided data with low computational resources. It is easy to customize or extend. Users can find their desired customizability from a smooth range.\n", - "* It supports fast and economical automatic tuning (e.g., inference hyperparameters for foundation models, configurations in MLOps/LMOps workflows, pipelines, mathematical/statistical models, algorithms, computing experiments, software configurations), capable of handling large search space with heterogeneous evaluation cost and complex constraints/guidance/early stopping.\n", + "- FLAML enables building next-gen GPT-X applications based on multi-agent conversations with minimal effort. It simplifies the orchestration, automation and optimization of a complex GPT-X workflow. It maximizes the performance of GPT-X models and augments their weakness.\n", + "- For common machine learning tasks like classification and regression, it quickly finds quality models for user-provided data with low computational resources. It is easy to customize or extend. Users can find their desired customizability from a smooth range.\n", + "- It supports fast and economical automatic tuning (e.g., inference hyperparameters for foundation models, configurations in MLOps/LMOps workflows, pipelines, mathematical/statistical models, algorithms, computing experiments, software configurations), capable of handling large search space with heterogeneous evaluation cost and complex constraints/guidance/early stopping.\n", "\n", "FLAML is powered by a series of [research studies](https://microsoft.github.io/FLAML/docs/Research/) from Microsoft Research and collaborators such as Penn State University, Stevens Institute of Technology, University of Washington, and University of Waterloo.\n", "\n", @@ -428,6 +447,7 @@ "```\n", "\n", "Minimal dependencies are installed without extra options. You can install extra options based on the feature you need. For example, use the following to install the dependencies needed by the [`autogen`](https://microsoft.github.io/autogen/) package.\n", + "\n", "```bash\n", "pip install \"flaml[autogen]\"\n", "```\n", @@ -437,18 +457,24 @@ "\n", "## Quickstart\n", "\n", - "* (New) The [autogen](https://microsoft.github.io/autogen/) package enables the next-gen GPT-X applications with a generic multi-agent conversation framework.\n", - "It offers customizable and conversable agents which integrate LLMs, tools and human.\n", - "By automating chat among multiple capable agents, one can easily make them collectively perform tasks autonomously or with human feedback, including tasks that require using tools via code. For example,\n", + "- (New) The [autogen](https://microsoft.github.io/autogen/) package enables the next-gen GPT-X applications with a generic multi-agent conversation framework.\n", + " It offers customizable and conversable agents which integrate LLMs, tools and human.\n", + " By automating chat among multiple capable agents, one can easily make them collectively perform tasks autonomously or with human feedback, including tasks that require using tools via code. For example,\n", + "\n", "```python\n", "from flaml import autogen\n", + "\n", "assistant = autogen.AssistantAgent(\"assistant\")\n", "user_proxy = autogen.UserProxyAgent(\"user_proxy\")\n", - "user_proxy.initiate_chat(assistant, message=\"Show me the YTD gain of 10 largest technology companies as of today.\")\n", + "user_proxy.initiate_chat(\n", + " assistant,\n", + " message=\"Show me the YTD gain of 10 largest technology companies as of today.\",\n", + ")\n", "# This initiates an automated chat between the two agents to solve the task\n", "```\n", "\n", "Autogen also helps maximize the utility out of the expensive LLMs such as ChatGPT and GPT-4. It offers a drop-in replacement of `openai.Completion` or `openai.ChatCompletion` with powerful functionalites like tuning, caching, templating, filtering. For example, you can optimize generations by LLM with your own tuning data, success metrics and budgets.\n", + "\n", "```python\n", "# perform tuning\n", "config, analysis = autogen.Completion.tune(\n", @@ -463,30 +489,32 @@ "# perform inference for a test instance\n", "response = autogen.Completion.create(context=test_instance, **config)\n", "```\n", - "* With three lines of code, you can start using this economical and fast\n", - "AutoML engine as a [scikit-learn style estimator](https://microsoft.github.io/FLAML/docs/Use-Cases/Task-Oriented-AutoML).\n", + "\n", + "- With three lines of code, you can start using this economical and fast\n", + " AutoML engine as a [scikit-learn style estimator](https://microsoft.github.io/FLAML/docs/Use-Cases/Task-Oriented-AutoML).\n", "\n", "```python\n", "from flaml import AutoML\n", + "\n", "automl = AutoML()\n", "automl.fit(X_train, y_train, task=\"classification\")\n", "```\n", "\n", - "* You can restrict the learners and use FLAML as a fast hyperparameter tuning\n", - "tool for XGBoost, LightGBM, Random Forest etc. or a [customized learner](https://microsoft.github.io/FLAML/docs/Use-Cases/Task-Oriented-AutoML#estimator-and-search-space).\n", + "- You can restrict the learners and use FLAML as a fast hyperparameter tuning\n", + " tool for XGBoost, LightGBM, Random Forest etc. or a [customized learner](https://microsoft.github.io/FLAML/docs/Use-Cases/Task-Oriented-AutoML#estimator-and-search-space).\n", "\n", "```python\n", "automl.fit(X_train, y_train, task=\"classification\", estimator_list=[\"lgbm\"])\n", "```\n", "\n", - "* You can also run generic hyperparameter tuning for a [custom function](https://microsoft.github.io/FLAML/docs/Use-Cases/Tune-User-Defined-Function).\n", + "- You can also run generic hyperparameter tuning for a [custom function](https://microsoft.github.io/FLAML/docs/Use-Cases/Tune-User-Defined-Function).\n", "\n", "```python\n", "from flaml import tune\n", "tune.run(evaluation_function, config={…}, low_cost_partial_config={…}, time_budget_s=3600)\n", "```\n", "\n", - "* [Zero-shot AutoML](https://microsoft.github.io/FLAML/docs/Use-Cases/Zero-Shot-AutoML) allows using the existing training API from lightgbm, xgboost etc. while getting the benefit of AutoML in choosing high-performance hyperparameter configurations per task.\n", + "- [Zero-shot AutoML](https://microsoft.github.io/FLAML/docs/Use-Cases/Zero-Shot-AutoML) allows using the existing training API from lightgbm, xgboost etc. while getting the benefit of AutoML in choosing high-performance hyperparameter configurations per task.\n", "\n", "```python\n", "from flaml.default import LGBMRegressor\n", @@ -517,12 +545,98 @@ "Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us\n", "the rights to use your contribution. For details, visit .\n", "\n", - "If you are new to GitHub [here](https://help.github.com/categories/collaborating-with-issues-and-pull-requests/) is a detailed help source on getting involved with development on GitHub.\n", - "# Research\n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33massistant\u001b[0m (to ragproxyagent):\n", + "\n", + "No, there is no function called `tune_automl` specifically mentioned in the context provided. However, FLAML does offer general hyperparameter tuning capabilities which could be related to this. In the context of FLAML, there is a generic function called `tune.run()` that can be used for hyperparameter tuning.\n", + "\n", + "Here's a short example of how to use FLAML's tune for a user-defined function based on the given context:\n", + "\n", + "```python\n", + "from flaml import tune\n", + "\n", + "def evaluation_function(config):\n", + " # evaluation logic that returns a metric score\n", + " ...\n", + "\n", + "# define the search space for hyperparameters\n", + "config_search_space = {\n", + " 'max_depth': tune.randint(lower=3, upper=10),\n", + " 'learning_rate': tune.loguniform(lower=1e-4, upper=1e-1),\n", + "}\n", + "\n", + "# run hyperparameter tuning\n", + "tune.run(\n", + " evaluation_function,\n", + " config=config_search_space,\n", + " low_cost_partial_config={'max_depth': 3},\n", + " time_budget_s=3600\n", + ")\n", + "```\n", + "\n", + "Please note that if you are referring to a different kind of function or use case, you might need to specify more details or check the official documentation or source code of the FLAML library.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mragproxyagent\u001b[0m (to assistant):\n", + "\n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33massistant\u001b[0m (to ragproxyagent):\n", + "\n", + "UPDATE CONTEXT\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[32mUpdating context and resetting conversation.\u001b[0m\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Model gpt4-1106-preview not found. Using cl100k_base encoding.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[32mAdding content of doc 2 to context.\u001b[0m\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Model gpt4-1106-preview not found. Using cl100k_base encoding.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[32mAdding content of doc 1 to context.\u001b[0m\n", + "\u001b[33mragproxyagent\u001b[0m (to assistant):\n", + "\n", + "You're a retrieve augmented coding assistant. You answer user's questions based on your own knowledge and the\n", + "context provided by the user.\n", + "If you can't answer the question with or without the current context, you should reply exactly `UPDATE CONTEXT`.\n", + "For code generation, you must obey the following rules:\n", + "Rule 1. You MUST NOT install any packages because all the packages needed are already installed.\n", + "Rule 2. You must follow the formats below to write your code:\n", + "```language\n", + "# your code\n", + "```\n", + "\n", + "User's question is: Is there a function called tune_automl?\n", + "\n", + "Context is: # Research\n", "\n", "For technical details, please check our research publications.\n", "\n", - "* [FLAML: A Fast and Lightweight AutoML Library](https://www.microsoft.com/en-us/research/publication/flaml-a-fast-and-lightweight-automl-library/). Chi Wang, Qingyun Wu, Markus Weimer, Erkang Zhu. MLSys 2021.\n", + "- [FLAML: A Fast and Lightweight AutoML Library](https://www.microsoft.com/en-us/research/publication/flaml-a-fast-and-lightweight-automl-library/). Chi Wang, Qingyun Wu, Markus Weimer, Erkang Zhu. MLSys 2021.\n", "\n", "```bibtex\n", "@inproceedings{wang2021flaml,\n", @@ -533,7 +647,7 @@ "}\n", "```\n", "\n", - "* [Frugal Optimization for Cost-related Hyperparameters](https://arxiv.org/abs/2005.01571). Qingyun Wu, Chi Wang, Silu Huang. AAAI 2021.\n", + "- [Frugal Optimization for Cost-related Hyperparameters](https://arxiv.org/abs/2005.01571). Qingyun Wu, Chi Wang, Silu Huang. AAAI 2021.\n", "\n", "```bibtex\n", "@inproceedings{wu2021cfo,\n", @@ -544,7 +658,7 @@ "}\n", "```\n", "\n", - "* [Economical Hyperparameter Optimization With Blended Search Strategy](https://www.microsoft.com/en-us/research/publication/economical-hyperparameter-optimization-with-blended-search-strategy/). Chi Wang, Qingyun Wu, Silu Huang, Amin Saied. ICLR 2021.\n", + "- [Economical Hyperparameter Optimization With Blended Search Strategy](https://www.microsoft.com/en-us/research/publication/economical-hyperparameter-optimization-with-blended-search-strategy/). Chi Wang, Qingyun Wu, Silu Huang, Amin Saied. ICLR 2021.\n", "\n", "```bibtex\n", "@inproceedings{wang2021blendsearch,\n", @@ -555,7 +669,7 @@ "}\n", "```\n", "\n", - "* [An Empirical Study on Hyperparameter Optimization for Fine-Tuning Pre-trained Language Models](https://aclanthology.org/2021.acl-long.178.pdf). Susan Xueqing Liu, Chi Wang. ACL 2021.\n", + "- [An Empirical Study on Hyperparameter Optimization for Fine-Tuning Pre-trained Language Models](https://aclanthology.org/2021.acl-long.178.pdf). Susan Xueqing Liu, Chi Wang. ACL 2021.\n", "\n", "```bibtex\n", "@inproceedings{liuwang2021hpolm,\n", @@ -566,7 +680,7 @@ "}\n", "```\n", "\n", - "* [ChaCha for Online AutoML](https://www.microsoft.com/en-us/research/publication/chacha-for-online-automl/). Qingyun Wu, Chi Wang, John Langford, Paul Mineiro and Marco Rossi. ICML 2021.\n", + "- [ChaCha for Online AutoML](https://www.microsoft.com/en-us/research/publication/chacha-for-online-automl/). Qingyun Wu, Chi Wang, John Langford, Paul Mineiro and Marco Rossi. ICML 2021.\n", "\n", "```bibtex\n", "@inproceedings{wu2021chacha,\n", @@ -577,7 +691,7 @@ "}\n", "```\n", "\n", - "* [Fair AutoML](https://arxiv.org/abs/2111.06495). Qingyun Wu, Chi Wang. ArXiv preprint arXiv:2111.06495 (2021).\n", + "- [Fair AutoML](https://arxiv.org/abs/2111.06495). Qingyun Wu, Chi Wang. ArXiv preprint arXiv:2111.06495 (2021).\n", "\n", "```bibtex\n", "@inproceedings{wuwang2021fairautoml,\n", @@ -588,7 +702,7 @@ "}\n", "```\n", "\n", - "* [Mining Robust Default Configurations for Resource-constrained AutoML](https://arxiv.org/abs/2202.09927). Moe Kayali, Chi Wang. ArXiv preprint arXiv:2202.09927 (2022).\n", + "- [Mining Robust Default Configurations for Resource-constrained AutoML](https://arxiv.org/abs/2202.09927). Moe Kayali, Chi Wang. ArXiv preprint arXiv:2202.09927 (2022).\n", "\n", "```bibtex\n", "@inproceedings{kayaliwang2022default,\n", @@ -599,7 +713,7 @@ "}\n", "```\n", "\n", - "* [Targeted Hyperparameter Optimization with Lexicographic Preferences Over Multiple Objectives](https://openreview.net/forum?id=0Ij9_q567Ma). Shaokun Zhang, Feiran Jia, Chi Wang, Qingyun Wu. ICLR 2023 (notable-top-5%).\n", + "- [Targeted Hyperparameter Optimization with Lexicographic Preferences Over Multiple Objectives](https://openreview.net/forum?id=0Ij9_q567Ma). Shaokun Zhang, Feiran Jia, Chi Wang, Qingyun Wu. ICLR 2023 (notable-top-5%).\n", "\n", "```bibtex\n", "@inproceedings{zhang2023targeted,\n", @@ -611,7 +725,7 @@ "}\n", "```\n", "\n", - "* [Cost-Effective Hyperparameter Optimization for Large Language Model Generation Inference](https://arxiv.org/abs/2303.04673). Chi Wang, Susan Xueqing Liu, Ahmed H. Awadallah. ArXiv preprint arXiv:2303.04673 (2023).\n", + "- [Cost-Effective Hyperparameter Optimization for Large Language Model Generation Inference](https://arxiv.org/abs/2303.04673). Chi Wang, Susan Xueqing Liu, Ahmed H. Awadallah. ArXiv preprint arXiv:2303.04673 (2023).\n", "\n", "```bibtex\n", "@inproceedings{wang2023EcoOptiGen,\n", @@ -622,7 +736,7 @@ "}\n", "```\n", "\n", - "* [An Empirical Study on Challenging Math Problem Solving with GPT-4](https://arxiv.org/abs/2306.01337). Yiran Wu, Feiran Jia, Shaokun Zhang, Hangyu Li, Erkang Zhu, Yue Wang, Yin Tat Lee, Richard Peng, Qingyun Wu, Chi Wang. ArXiv preprint arXiv:2306.01337 (2023).\n", + "- [An Empirical Study on Challenging Math Problem Solving with GPT-4](https://arxiv.org/abs/2306.01337). Yiran Wu, Feiran Jia, Shaokun Zhang, Hangyu Li, Erkang Zhu, Yue Wang, Yin Tat Lee, Richard Peng, Qingyun Wu, Chi Wang. ArXiv preprint arXiv:2306.01337 (2023).\n", "\n", "```bibtex\n", "@inproceedings{wu2023empirical,\n", @@ -632,7 +746,7 @@ " booktitle={ArXiv preprint arXiv:2306.01337},\n", "}\n", "```\n", - "\n", + "If you are new to GitHub [here](https://help.github.com/categories/collaborating-with-issues-and-pull-requests/) is a detailed help source on getting involved with development on GitHub.\n", "\n", "When you submit a pull request, a CLA bot will automatically determine whether you need to provide\n", "a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions\n", @@ -644,26 +758,10 @@ "\n", "\n", "\n", - "\n", "--------------------------------------------------------------------------------\n", "\u001b[33massistant\u001b[0m (to ragproxyagent):\n", "\n", - "Based on the context provided, which is about the FLAML library, there is no direct reference to a function specifically called `tune_automl`. However, FLAML does offer functionality for automated machine learning (AutoML) and hyperparameter tuning.\n", - "\n", - "The closest reference to an AutoML tuning operation in the given context is shown in the Quickstart section, which demonstrates how to use FLAML as a scikit-learn style estimator for machine learning tasks like classification and regression. It does talk about automated machine learning and tuning, but doesn't mention a function `tune_automl` by name.\n", - "\n", - "If you are looking for a way to perform tuning with FLAML, the context indicates you can use the `tune` module to run generic hyperparameter tuning for a custom function, as demonstrated in the Quickstart section:\n", - "\n", - "```python\n", - "from flaml import tune\n", - "tune.run(evaluation_function, config={…}, low_cost_partial_config={…}, time_budget_s=3600)\n", - "```\n", - "\n", - "This is not called `tune_automl` but rather just `tune.run`.\n", - "\n", - "If you need confirmation on whether a function called `tune_automl` specifically exists, the FLAML documentation or its API reference should be checked. If documentation is not enough to confirm and you require to look into the actual code or a structured list of functionalities provided by FLAML, that information isn't available in the given context.\n", - "\n", - "In that case, the instruction should be: `UPDATE CONTEXT`.\n", + "UPDATE CONTEXT\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[32mUpdating context and resetting conversation.\u001b[0m\n", @@ -678,10 +776,10 @@ { "data": { "text/plain": [ - "ChatResult(chat_id=None, chat_history=[{'content': 'TERMINATE', 'role': 'assistant'}], summary='', cost=({'total_cost': 0.12719999999999998, 'gpt-4': {'cost': 0.12719999999999998, 'prompt_tokens': 3634, 'completion_tokens': 303, 'total_tokens': 3937}}, {'total_cost': 0.12719999999999998, 'gpt-4': {'cost': 0.12719999999999998, 'prompt_tokens': 3634, 'completion_tokens': 303, 'total_tokens': 3937}}), human_input=[])" + "ChatResult(chat_id=None, chat_history=[{'content': 'TERMINATE', 'role': 'assistant'}], summary='', cost=({'total_cost': 0.19977, 'gpt-4': {'cost': 0.19977, 'prompt_tokens': 6153, 'completion_tokens': 253, 'total_tokens': 6406}}, {'total_cost': 0.19977, 'gpt-4': {'cost': 0.19977, 'prompt_tokens': 6153, 'completion_tokens': 253, 'total_tokens': 6406}}), human_input=[])" ] }, - "execution_count": 20, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" } @@ -711,16 +809,34 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 12, "metadata": {}, "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Model gpt4-1106-preview not found. Using cl100k_base encoding.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[32mAdding content of doc 2 to context.\u001b[0m\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Model gpt4-1106-preview not found. Using cl100k_base encoding.\n" + ] + }, { "name": "stdout", "output_type": "stream", "text": [ - "\u001b[32mAdding doc_id 2 to context.\u001b[0m\n", - "\u001b[32mAdding doc_id 0 to context.\u001b[0m\n", - "\u001b[32mAdding doc_id 1 to context.\u001b[0m\n", "\u001b[33mragproxyagent\u001b[0m (to assistant):\n", "\n", "You're a retrieve augmented coding assistant. You answer user's questions based on your own knowledge and the\n", @@ -739,7 +855,7 @@ "\n", "For technical details, please check our research publications.\n", "\n", - "* [FLAML: A Fast and Lightweight AutoML Library](https://www.microsoft.com/en-us/research/publication/flaml-a-fast-and-lightweight-automl-library/). Chi Wang, Qingyun Wu, Markus Weimer, Erkang Zhu. MLSys 2021.\n", + "- [FLAML: A Fast and Lightweight AutoML Library](https://www.microsoft.com/en-us/research/publication/flaml-a-fast-and-lightweight-automl-library/). Chi Wang, Qingyun Wu, Markus Weimer, Erkang Zhu. MLSys 2021.\n", "\n", "```bibtex\n", "@inproceedings{wang2021flaml,\n", @@ -750,7 +866,7 @@ "}\n", "```\n", "\n", - "* [Frugal Optimization for Cost-related Hyperparameters](https://arxiv.org/abs/2005.01571). Qingyun Wu, Chi Wang, Silu Huang. AAAI 2021.\n", + "- [Frugal Optimization for Cost-related Hyperparameters](https://arxiv.org/abs/2005.01571). Qingyun Wu, Chi Wang, Silu Huang. AAAI 2021.\n", "\n", "```bibtex\n", "@inproceedings{wu2021cfo,\n", @@ -761,7 +877,7 @@ "}\n", "```\n", "\n", - "* [Economical Hyperparameter Optimization With Blended Search Strategy](https://www.microsoft.com/en-us/research/publication/economical-hyperparameter-optimization-with-blended-search-strategy/). Chi Wang, Qingyun Wu, Silu Huang, Amin Saied. ICLR 2021.\n", + "- [Economical Hyperparameter Optimization With Blended Search Strategy](https://www.microsoft.com/en-us/research/publication/economical-hyperparameter-optimization-with-blended-search-strategy/). Chi Wang, Qingyun Wu, Silu Huang, Amin Saied. ICLR 2021.\n", "\n", "```bibtex\n", "@inproceedings{wang2021blendsearch,\n", @@ -772,7 +888,7 @@ "}\n", "```\n", "\n", - "* [An Empirical Study on Hyperparameter Optimization for Fine-Tuning Pre-trained Language Models](https://aclanthology.org/2021.acl-long.178.pdf). Susan Xueqing Liu, Chi Wang. ACL 2021.\n", + "- [An Empirical Study on Hyperparameter Optimization for Fine-Tuning Pre-trained Language Models](https://aclanthology.org/2021.acl-long.178.pdf). Susan Xueqing Liu, Chi Wang. ACL 2021.\n", "\n", "```bibtex\n", "@inproceedings{liuwang2021hpolm,\n", @@ -783,7 +899,7 @@ "}\n", "```\n", "\n", - "* [ChaCha for Online AutoML](https://www.microsoft.com/en-us/research/publication/chacha-for-online-automl/). Qingyun Wu, Chi Wang, John Langford, Paul Mineiro and Marco Rossi. ICML 2021.\n", + "- [ChaCha for Online AutoML](https://www.microsoft.com/en-us/research/publication/chacha-for-online-automl/). Qingyun Wu, Chi Wang, John Langford, Paul Mineiro and Marco Rossi. ICML 2021.\n", "\n", "```bibtex\n", "@inproceedings{wu2021chacha,\n", @@ -794,7 +910,7 @@ "}\n", "```\n", "\n", - "* [Fair AutoML](https://arxiv.org/abs/2111.06495). Qingyun Wu, Chi Wang. ArXiv preprint arXiv:2111.06495 (2021).\n", + "- [Fair AutoML](https://arxiv.org/abs/2111.06495). Qingyun Wu, Chi Wang. ArXiv preprint arXiv:2111.06495 (2021).\n", "\n", "```bibtex\n", "@inproceedings{wuwang2021fairautoml,\n", @@ -805,7 +921,7 @@ "}\n", "```\n", "\n", - "* [Mining Robust Default Configurations for Resource-constrained AutoML](https://arxiv.org/abs/2202.09927). Moe Kayali, Chi Wang. ArXiv preprint arXiv:2202.09927 (2022).\n", + "- [Mining Robust Default Configurations for Resource-constrained AutoML](https://arxiv.org/abs/2202.09927). Moe Kayali, Chi Wang. ArXiv preprint arXiv:2202.09927 (2022).\n", "\n", "```bibtex\n", "@inproceedings{kayaliwang2022default,\n", @@ -816,7 +932,7 @@ "}\n", "```\n", "\n", - "* [Targeted Hyperparameter Optimization with Lexicographic Preferences Over Multiple Objectives](https://openreview.net/forum?id=0Ij9_q567Ma). Shaokun Zhang, Feiran Jia, Chi Wang, Qingyun Wu. ICLR 2023 (notable-top-5%).\n", + "- [Targeted Hyperparameter Optimization with Lexicographic Preferences Over Multiple Objectives](https://openreview.net/forum?id=0Ij9_q567Ma). Shaokun Zhang, Feiran Jia, Chi Wang, Qingyun Wu. ICLR 2023 (notable-top-5%).\n", "\n", "```bibtex\n", "@inproceedings{zhang2023targeted,\n", @@ -828,7 +944,7 @@ "}\n", "```\n", "\n", - "* [Cost-Effective Hyperparameter Optimization for Large Language Model Generation Inference](https://arxiv.org/abs/2303.04673). Chi Wang, Susan Xueqing Liu, Ahmed H. Awadallah. ArXiv preprint arXiv:2303.04673 (2023).\n", + "- [Cost-Effective Hyperparameter Optimization for Large Language Model Generation Inference](https://arxiv.org/abs/2303.04673). Chi Wang, Susan Xueqing Liu, Ahmed H. Awadallah. ArXiv preprint arXiv:2303.04673 (2023).\n", "\n", "```bibtex\n", "@inproceedings{wang2023EcoOptiGen,\n", @@ -839,7 +955,7 @@ "}\n", "```\n", "\n", - "* [An Empirical Study on Challenging Math Problem Solving with GPT-4](https://arxiv.org/abs/2306.01337). Yiran Wu, Feiran Jia, Shaokun Zhang, Hangyu Li, Erkang Zhu, Yue Wang, Yin Tat Lee, Richard Peng, Qingyun Wu, Chi Wang. ArXiv preprint arXiv:2306.01337 (2023).\n", + "- [An Empirical Study on Challenging Math Problem Solving with GPT-4](https://arxiv.org/abs/2306.01337). Yiran Wu, Feiran Jia, Shaokun Zhang, Hangyu Li, Erkang Zhu, Yue Wang, Yin Tat Lee, Richard Peng, Qingyun Wu, Chi Wang. ArXiv preprint arXiv:2306.01337 (2023).\n", "\n", "```bibtex\n", "@inproceedings{wu2023empirical,\n", @@ -850,161 +966,18 @@ "}\n", "```\n", "\n", - "[![PyPI version](https://badge.fury.io/py/FLAML.svg)](https://badge.fury.io/py/FLAML)\n", - "![Conda version](https://img.shields.io/conda/vn/conda-forge/flaml)\n", - "[![Build](https://github.com/microsoft/FLAML/actions/workflows/python-package.yml/badge.svg)](https://github.com/microsoft/FLAML/actions/workflows/python-package.yml)\n", - "![Python Version](https://img.shields.io/badge/3.8%20%7C%203.9%20%7C%203.10-blue)\n", - "[![Downloads](https://pepy.tech/badge/flaml)](https://pepy.tech/project/flaml)\n", - "[![](https://img.shields.io/discord/1025786666260111483?logo=discord&style=flat)](https://discord.gg/Cppx2vSPVP)\n", - "\n", "\n", "\n", - "# A Fast Library for Automated Machine Learning & Tuning\n", - "\n", - "

\n", - " \n", - "
\n", - "

\n", - "\n", - ":fire: Heads-up: We have migrated [AutoGen](https://microsoft.github.io/autogen/) into a dedicated [github repository](https://github.com/microsoft/autogen). Alongside this move, we have also launched a dedicated [Discord](https://discord.gg/pAbnFJrkgZ) server and a [website](https://microsoft.github.io/autogen/) for comprehensive documentation.\n", - "\n", - ":fire: The automated multi-agent chat framework in [AutoGen](https://microsoft.github.io/autogen/) is in preview from v2.0.0.\n", - "\n", - ":fire: FLAML is highlighted in OpenAI's [cookbook](https://github.com/openai/openai-cookbook#related-resources-from-around-the-web).\n", - "\n", - ":fire: [autogen](https://microsoft.github.io/autogen/) is released with support for ChatGPT and GPT-4, based on [Cost-Effective Hyperparameter Optimization for Large Language Model Generation Inference](https://arxiv.org/abs/2303.04673).\n", - "\n", - ":fire: FLAML supports Code-First AutoML & Tuning – Private Preview in [Microsoft Fabric Data Science](https://learn.microsoft.com/en-us/fabric/data-science/).\n", - "\n", - "\n", - "## What is FLAML\n", - "FLAML is a lightweight Python library for efficient automation of machine\n", - "learning and AI operations. It automates workflow based on large language models, machine learning models, etc.\n", - "and optimizes their performance.\n", - "\n", - "* FLAML enables building next-gen GPT-X applications based on multi-agent conversations with minimal effort. It simplifies the orchestration, automation and optimization of a complex GPT-X workflow. It maximizes the performance of GPT-X models and augments their weakness.\n", - "* For common machine learning tasks like classification and regression, it quickly finds quality models for user-provided data with low computational resources. It is easy to customize or extend. Users can find their desired customizability from a smooth range.\n", - "* It supports fast and economical automatic tuning (e.g., inference hyperparameters for foundation models, configurations in MLOps/LMOps workflows, pipelines, mathematical/statistical models, algorithms, computing experiments, software configurations), capable of handling large search space with heterogeneous evaluation cost and complex constraints/guidance/early stopping.\n", - "\n", - "FLAML is powered by a series of [research studies](https://microsoft.github.io/FLAML/docs/Research/) from Microsoft Research and collaborators such as Penn State University, Stevens Institute of Technology, University of Washington, and University of Waterloo.\n", - "\n", - "FLAML has a .NET implementation in [ML.NET](http://dot.net/ml), an open-source, cross-platform machine learning framework for .NET.\n", - "\n", - "## Installation\n", - "\n", - "FLAML requires **Python version >= 3.8**. It can be installed from pip:\n", - "\n", - "```bash\n", - "pip install flaml\n", - "```\n", - "\n", - "Minimal dependencies are installed without extra options. You can install extra options based on the feature you need. For example, use the following to install the dependencies needed by the [`autogen`](https://microsoft.github.io/autogen/) package.\n", - "```bash\n", - "pip install \"flaml[autogen]\"\n", - "```\n", - "\n", - "Find more options in [Installation](https://microsoft.github.io/FLAML/docs/Installation).\n", - "Each of the [`notebook examples`](https://github.com/microsoft/FLAML/tree/main/notebook) may require a specific option to be installed.\n", - "\n", - "## Quickstart\n", - "\n", - "* (New) The [autogen](https://microsoft.github.io/autogen/) package enables the next-gen GPT-X applications with a generic multi-agent conversation framework.\n", - "It offers customizable and conversable agents which integrate LLMs, tools and human.\n", - "By automating chat among multiple capable agents, one can easily make them collectively perform tasks autonomously or with human feedback, including tasks that require using tools via code. For example,\n", - "```python\n", - "from flaml import autogen\n", - "assistant = autogen.AssistantAgent(\"assistant\")\n", - "user_proxy = autogen.UserProxyAgent(\"user_proxy\")\n", - "user_proxy.initiate_chat(assistant, message=\"Show me the YTD gain of 10 largest technology companies as of today.\")\n", - "# This initiates an automated chat between the two agents to solve the task\n", - "```\n", - "\n", - "Autogen also helps maximize the utility out of the expensive LLMs such as ChatGPT and GPT-4. It offers a drop-in replacement of `openai.Completion` or `openai.ChatCompletion` with powerful functionalites like tuning, caching, templating, filtering. For example, you can optimize generations by LLM with your own tuning data, success metrics and budgets.\n", - "```python\n", - "# perform tuning\n", - "config, analysis = autogen.Completion.tune(\n", - " data=tune_data,\n", - " metric=\"success\",\n", - " mode=\"max\",\n", - " eval_func=eval_func,\n", - " inference_budget=0.05,\n", - " optimization_budget=3,\n", - " num_samples=-1,\n", - ")\n", - "# perform inference for a test instance\n", - "response = autogen.Completion.create(context=test_instance, **config)\n", - "```\n", - "* With three lines of code, you can start using this economical and fast\n", - "AutoML engine as a [scikit-learn style estimator](https://microsoft.github.io/FLAML/docs/Use-Cases/Task-Oriented-AutoML).\n", - "\n", - "```python\n", - "from flaml import AutoML\n", - "automl = AutoML()\n", - "automl.fit(X_train, y_train, task=\"classification\")\n", - "```\n", - "\n", - "* You can restrict the learners and use FLAML as a fast hyperparameter tuning\n", - "tool for XGBoost, LightGBM, Random Forest etc. or a [customized learner](https://microsoft.github.io/FLAML/docs/Use-Cases/Task-Oriented-AutoML#estimator-and-search-space).\n", - "\n", - "```python\n", - "automl.fit(X_train, y_train, task=\"classification\", estimator_list=[\"lgbm\"])\n", - "```\n", - "\n", - "* You can also run generic hyperparameter tuning for a [custom function](https://microsoft.github.io/FLAML/docs/Use-Cases/Tune-User-Defined-Function).\n", - "\n", - "```python\n", - "from flaml import tune\n", - "tune.run(evaluation_function, config={…}, low_cost_partial_config={…}, time_budget_s=3600)\n", - "```\n", - "\n", - "* [Zero-shot AutoML](https://microsoft.github.io/FLAML/docs/Use-Cases/Zero-Shot-AutoML) allows using the existing training API from lightgbm, xgboost etc. while getting the benefit of AutoML in choosing high-performance hyperparameter configurations per task.\n", - "\n", - "```python\n", - "from flaml.default import LGBMRegressor\n", - "\n", - "# Use LGBMRegressor in the same way as you use lightgbm.LGBMRegressor.\n", - "estimator = LGBMRegressor()\n", - "# The hyperparameters are automatically set according to the training data.\n", - "estimator.fit(X_train, y_train)\n", - "```\n", - "\n", - "## Documentation\n", - "\n", - "You can find a detailed documentation about FLAML [here](https://microsoft.github.io/FLAML/).\n", - "\n", - "In addition, you can find:\n", - "\n", - "- [Research](https://microsoft.github.io/FLAML/docs/Research) and [blogposts](https://microsoft.github.io/FLAML/blog) around FLAML.\n", - "\n", - "- [Discord](https://discord.gg/Cppx2vSPVP).\n", - "\n", - "- [Contributing guide](https://microsoft.github.io/FLAML/docs/Contribute).\n", - "\n", - "- ML.NET documentation and tutorials for [Model Builder](https://learn.microsoft.com/dotnet/machine-learning/tutorials/predict-prices-with-model-builder), [ML.NET CLI](https://learn.microsoft.com/dotnet/machine-learning/tutorials/sentiment-analysis-cli), and [AutoML API](https://learn.microsoft.com/dotnet/machine-learning/how-to-guides/how-to-use-the-automl-api).\n", - "\n", - "## Contributing\n", - "\n", - "This project welcomes contributions and suggestions. Most contributions require you to agree to a\n", - "Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us\n", - "the rights to use your contribution. For details, visit .\n", - "\n", - "If you are new to GitHub [here](https://help.github.com/categories/collaborating-with-issues-and-pull-requests/) is a detailed help source on getting involved with development on GitHub.\n", - "\n", - "When you submit a pull request, a CLA bot will automatically determine whether you need to provide\n", - "a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions\n", - "provided by the bot. You will only need to do this once across all repos using our CLA.\n", - "\n", - "This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).\n", - "For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or\n", - "contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.\n", - "\n", - "\n", - "\n", - "\n", - "--------------------------------------------------------------------------------\n", + "--------------------------------------------------------------------------------\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ "\u001b[33massistant\u001b[0m (to ragproxyagent):\n", "\n", - "The author of FLAML is Chi Wang, along with other collaborators including Qingyun Wu, Markus Weimer, Erkang Zhu, Silu Huang, Amin Saied, Susan Xueqing Liu, John Langford, Paul Mineiro, Marco Rossi, Moe Kayali, Shaokun Zhang, Feiran Jia, Yiran Wu, Hangyu Li, Yue Wang, Yin Tat Lee, Richard Peng, and Ahmed H. Awadallah, as indicated in the provided references for FLAML's research publications.\n", + "The authors of FLAML are Chi Wang, Qingyun Wu, Markus Weimer, and Erkang Zhu.\n", "\n", "--------------------------------------------------------------------------------\n" ] @@ -1012,10 +985,10 @@ { "data": { "text/plain": [ - "ChatResult(chat_id=None, chat_history=[{'content': 'You\\'re a retrieve augmented coding assistant. You answer user\\'s questions based on your own knowledge and the\\ncontext provided by the user.\\nIf you can\\'t answer the question with or without the current context, you should reply exactly `UPDATE CONTEXT`.\\nFor code generation, you must obey the following rules:\\nRule 1. You MUST NOT install any packages because all the packages needed are already installed.\\nRule 2. You must follow the formats below to write your code:\\n```language\\n# your code\\n```\\n\\nUser\\'s question is: Who is the author of FLAML?\\n\\nContext is: # Research\\n\\nFor technical details, please check our research publications.\\n\\n* [FLAML: A Fast and Lightweight AutoML Library](https://www.microsoft.com/en-us/research/publication/flaml-a-fast-and-lightweight-automl-library/). Chi Wang, Qingyun Wu, Markus Weimer, Erkang Zhu. MLSys 2021.\\n\\n```bibtex\\n@inproceedings{wang2021flaml,\\n title={FLAML: A Fast and Lightweight AutoML Library},\\n author={Chi Wang and Qingyun Wu and Markus Weimer and Erkang Zhu},\\n year={2021},\\n booktitle={MLSys},\\n}\\n```\\n\\n* [Frugal Optimization for Cost-related Hyperparameters](https://arxiv.org/abs/2005.01571). Qingyun Wu, Chi Wang, Silu Huang. AAAI 2021.\\n\\n```bibtex\\n@inproceedings{wu2021cfo,\\n title={Frugal Optimization for Cost-related Hyperparameters},\\n author={Qingyun Wu and Chi Wang and Silu Huang},\\n year={2021},\\n booktitle={AAAI},\\n}\\n```\\n\\n* [Economical Hyperparameter Optimization With Blended Search Strategy](https://www.microsoft.com/en-us/research/publication/economical-hyperparameter-optimization-with-blended-search-strategy/). Chi Wang, Qingyun Wu, Silu Huang, Amin Saied. ICLR 2021.\\n\\n```bibtex\\n@inproceedings{wang2021blendsearch,\\n title={Economical Hyperparameter Optimization With Blended Search Strategy},\\n author={Chi Wang and Qingyun Wu and Silu Huang and Amin Saied},\\n year={2021},\\n booktitle={ICLR},\\n}\\n```\\n\\n* [An Empirical Study on Hyperparameter Optimization for Fine-Tuning Pre-trained Language Models](https://aclanthology.org/2021.acl-long.178.pdf). Susan Xueqing Liu, Chi Wang. ACL 2021.\\n\\n```bibtex\\n@inproceedings{liuwang2021hpolm,\\n title={An Empirical Study on Hyperparameter Optimization for Fine-Tuning Pre-trained Language Models},\\n author={Susan Xueqing Liu and Chi Wang},\\n year={2021},\\n booktitle={ACL},\\n}\\n```\\n\\n* [ChaCha for Online AutoML](https://www.microsoft.com/en-us/research/publication/chacha-for-online-automl/). Qingyun Wu, Chi Wang, John Langford, Paul Mineiro and Marco Rossi. ICML 2021.\\n\\n```bibtex\\n@inproceedings{wu2021chacha,\\n title={ChaCha for Online AutoML},\\n author={Qingyun Wu and Chi Wang and John Langford and Paul Mineiro and Marco Rossi},\\n year={2021},\\n booktitle={ICML},\\n}\\n```\\n\\n* [Fair AutoML](https://arxiv.org/abs/2111.06495). Qingyun Wu, Chi Wang. ArXiv preprint arXiv:2111.06495 (2021).\\n\\n```bibtex\\n@inproceedings{wuwang2021fairautoml,\\n title={Fair AutoML},\\n author={Qingyun Wu and Chi Wang},\\n year={2021},\\n booktitle={ArXiv preprint arXiv:2111.06495},\\n}\\n```\\n\\n* [Mining Robust Default Configurations for Resource-constrained AutoML](https://arxiv.org/abs/2202.09927). Moe Kayali, Chi Wang. ArXiv preprint arXiv:2202.09927 (2022).\\n\\n```bibtex\\n@inproceedings{kayaliwang2022default,\\n title={Mining Robust Default Configurations for Resource-constrained AutoML},\\n author={Moe Kayali and Chi Wang},\\n year={2022},\\n booktitle={ArXiv preprint arXiv:2202.09927},\\n}\\n```\\n\\n* [Targeted Hyperparameter Optimization with Lexicographic Preferences Over Multiple Objectives](https://openreview.net/forum?id=0Ij9_q567Ma). Shaokun Zhang, Feiran Jia, Chi Wang, Qingyun Wu. ICLR 2023 (notable-top-5%).\\n\\n```bibtex\\n@inproceedings{zhang2023targeted,\\n title={Targeted Hyperparameter Optimization with Lexicographic Preferences Over Multiple Objectives},\\n author={Shaokun Zhang and Feiran Jia and Chi Wang and Qingyun Wu},\\n booktitle={International Conference on Learning Representations},\\n year={2023},\\n url={https://openreview.net/forum?id=0Ij9_q567Ma},\\n}\\n```\\n\\n* [Cost-Effective Hyperparameter Optimization for Large Language Model Generation Inference](https://arxiv.org/abs/2303.04673). Chi Wang, Susan Xueqing Liu, Ahmed H. Awadallah. ArXiv preprint arXiv:2303.04673 (2023).\\n\\n```bibtex\\n@inproceedings{wang2023EcoOptiGen,\\n title={Cost-Effective Hyperparameter Optimization for Large Language Model Generation Inference},\\n author={Chi Wang and Susan Xueqing Liu and Ahmed H. Awadallah},\\n year={2023},\\n booktitle={ArXiv preprint arXiv:2303.04673},\\n}\\n```\\n\\n* [An Empirical Study on Challenging Math Problem Solving with GPT-4](https://arxiv.org/abs/2306.01337). Yiran Wu, Feiran Jia, Shaokun Zhang, Hangyu Li, Erkang Zhu, Yue Wang, Yin Tat Lee, Richard Peng, Qingyun Wu, Chi Wang. ArXiv preprint arXiv:2306.01337 (2023).\\n\\n```bibtex\\n@inproceedings{wu2023empirical,\\n title={An Empirical Study on Challenging Math Problem Solving with GPT-4},\\n author={Yiran Wu and Feiran Jia and Shaokun Zhang and Hangyu Li and Erkang Zhu and Yue Wang and Yin Tat Lee and Richard Peng and Qingyun Wu and Chi Wang},\\n year={2023},\\n booktitle={ArXiv preprint arXiv:2306.01337},\\n}\\n```\\n\\n[![PyPI version](https://badge.fury.io/py/FLAML.svg)](https://badge.fury.io/py/FLAML)\\n![Conda version](https://img.shields.io/conda/vn/conda-forge/flaml)\\n[![Build](https://github.com/microsoft/FLAML/actions/workflows/python-package.yml/badge.svg)](https://github.com/microsoft/FLAML/actions/workflows/python-package.yml)\\n![Python Version](https://img.shields.io/badge/3.8%20%7C%203.9%20%7C%203.10-blue)\\n[![Downloads](https://pepy.tech/badge/flaml)](https://pepy.tech/project/flaml)\\n[![](https://img.shields.io/discord/1025786666260111483?logo=discord&style=flat)](https://discord.gg/Cppx2vSPVP)\\n\\n\\n\\n# A Fast Library for Automated Machine Learning & Tuning\\n\\n

\\n \\n
\\n

\\n\\n:fire: Heads-up: We have migrated [AutoGen](https://microsoft.github.io/autogen/) into a dedicated [github repository](https://github.com/microsoft/autogen). Alongside this move, we have also launched a dedicated [Discord](https://discord.gg/pAbnFJrkgZ) server and a [website](https://microsoft.github.io/autogen/) for comprehensive documentation.\\n\\n:fire: The automated multi-agent chat framework in [AutoGen](https://microsoft.github.io/autogen/) is in preview from v2.0.0.\\n\\n:fire: FLAML is highlighted in OpenAI\\'s [cookbook](https://github.com/openai/openai-cookbook#related-resources-from-around-the-web).\\n\\n:fire: [autogen](https://microsoft.github.io/autogen/) is released with support for ChatGPT and GPT-4, based on [Cost-Effective Hyperparameter Optimization for Large Language Model Generation Inference](https://arxiv.org/abs/2303.04673).\\n\\n:fire: FLAML supports Code-First AutoML & Tuning – Private Preview in [Microsoft Fabric Data Science](https://learn.microsoft.com/en-us/fabric/data-science/).\\n\\n\\n## What is FLAML\\nFLAML is a lightweight Python library for efficient automation of machine\\nlearning and AI operations. It automates workflow based on large language models, machine learning models, etc.\\nand optimizes their performance.\\n\\n* FLAML enables building next-gen GPT-X applications based on multi-agent conversations with minimal effort. It simplifies the orchestration, automation and optimization of a complex GPT-X workflow. It maximizes the performance of GPT-X models and augments their weakness.\\n* For common machine learning tasks like classification and regression, it quickly finds quality models for user-provided data with low computational resources. It is easy to customize or extend. Users can find their desired customizability from a smooth range.\\n* It supports fast and economical automatic tuning (e.g., inference hyperparameters for foundation models, configurations in MLOps/LMOps workflows, pipelines, mathematical/statistical models, algorithms, computing experiments, software configurations), capable of handling large search space with heterogeneous evaluation cost and complex constraints/guidance/early stopping.\\n\\nFLAML is powered by a series of [research studies](https://microsoft.github.io/FLAML/docs/Research/) from Microsoft Research and collaborators such as Penn State University, Stevens Institute of Technology, University of Washington, and University of Waterloo.\\n\\nFLAML has a .NET implementation in [ML.NET](http://dot.net/ml), an open-source, cross-platform machine learning framework for .NET.\\n\\n## Installation\\n\\nFLAML requires **Python version >= 3.8**. It can be installed from pip:\\n\\n```bash\\npip install flaml\\n```\\n\\nMinimal dependencies are installed without extra options. You can install extra options based on the feature you need. For example, use the following to install the dependencies needed by the [`autogen`](https://microsoft.github.io/autogen/) package.\\n```bash\\npip install \"flaml[autogen]\"\\n```\\n\\nFind more options in [Installation](https://microsoft.github.io/FLAML/docs/Installation).\\nEach of the [`notebook examples`](https://github.com/microsoft/FLAML/tree/main/notebook) may require a specific option to be installed.\\n\\n## Quickstart\\n\\n* (New) The [autogen](https://microsoft.github.io/autogen/) package enables the next-gen GPT-X applications with a generic multi-agent conversation framework.\\nIt offers customizable and conversable agents which integrate LLMs, tools and human.\\nBy automating chat among multiple capable agents, one can easily make them collectively perform tasks autonomously or with human feedback, including tasks that require using tools via code. For example,\\n```python\\nfrom flaml import autogen\\nassistant = autogen.AssistantAgent(\"assistant\")\\nuser_proxy = autogen.UserProxyAgent(\"user_proxy\")\\nuser_proxy.initiate_chat(assistant, message=\"Show me the YTD gain of 10 largest technology companies as of today.\")\\n# This initiates an automated chat between the two agents to solve the task\\n```\\n\\nAutogen also helps maximize the utility out of the expensive LLMs such as ChatGPT and GPT-4. It offers a drop-in replacement of `openai.Completion` or `openai.ChatCompletion` with powerful functionalites like tuning, caching, templating, filtering. For example, you can optimize generations by LLM with your own tuning data, success metrics and budgets.\\n```python\\n# perform tuning\\nconfig, analysis = autogen.Completion.tune(\\n data=tune_data,\\n metric=\"success\",\\n mode=\"max\",\\n eval_func=eval_func,\\n inference_budget=0.05,\\n optimization_budget=3,\\n num_samples=-1,\\n)\\n# perform inference for a test instance\\nresponse = autogen.Completion.create(context=test_instance, **config)\\n```\\n* With three lines of code, you can start using this economical and fast\\nAutoML engine as a [scikit-learn style estimator](https://microsoft.github.io/FLAML/docs/Use-Cases/Task-Oriented-AutoML).\\n\\n```python\\nfrom flaml import AutoML\\nautoml = AutoML()\\nautoml.fit(X_train, y_train, task=\"classification\")\\n```\\n\\n* You can restrict the learners and use FLAML as a fast hyperparameter tuning\\ntool for XGBoost, LightGBM, Random Forest etc. or a [customized learner](https://microsoft.github.io/FLAML/docs/Use-Cases/Task-Oriented-AutoML#estimator-and-search-space).\\n\\n```python\\nautoml.fit(X_train, y_train, task=\"classification\", estimator_list=[\"lgbm\"])\\n```\\n\\n* You can also run generic hyperparameter tuning for a [custom function](https://microsoft.github.io/FLAML/docs/Use-Cases/Tune-User-Defined-Function).\\n\\n```python\\nfrom flaml import tune\\ntune.run(evaluation_function, config={…}, low_cost_partial_config={…}, time_budget_s=3600)\\n```\\n\\n* [Zero-shot AutoML](https://microsoft.github.io/FLAML/docs/Use-Cases/Zero-Shot-AutoML) allows using the existing training API from lightgbm, xgboost etc. while getting the benefit of AutoML in choosing high-performance hyperparameter configurations per task.\\n\\n```python\\nfrom flaml.default import LGBMRegressor\\n\\n# Use LGBMRegressor in the same way as you use lightgbm.LGBMRegressor.\\nestimator = LGBMRegressor()\\n# The hyperparameters are automatically set according to the training data.\\nestimator.fit(X_train, y_train)\\n```\\n\\n## Documentation\\n\\nYou can find a detailed documentation about FLAML [here](https://microsoft.github.io/FLAML/).\\n\\nIn addition, you can find:\\n\\n- [Research](https://microsoft.github.io/FLAML/docs/Research) and [blogposts](https://microsoft.github.io/FLAML/blog) around FLAML.\\n\\n- [Discord](https://discord.gg/Cppx2vSPVP).\\n\\n- [Contributing guide](https://microsoft.github.io/FLAML/docs/Contribute).\\n\\n- ML.NET documentation and tutorials for [Model Builder](https://learn.microsoft.com/dotnet/machine-learning/tutorials/predict-prices-with-model-builder), [ML.NET CLI](https://learn.microsoft.com/dotnet/machine-learning/tutorials/sentiment-analysis-cli), and [AutoML API](https://learn.microsoft.com/dotnet/machine-learning/how-to-guides/how-to-use-the-automl-api).\\n\\n## Contributing\\n\\nThis project welcomes contributions and suggestions. Most contributions require you to agree to a\\nContributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us\\nthe rights to use your contribution. For details, visit .\\n\\nIf you are new to GitHub [here](https://help.github.com/categories/collaborating-with-issues-and-pull-requests/) is a detailed help source on getting involved with development on GitHub.\\n\\nWhen you submit a pull request, a CLA bot will automatically determine whether you need to provide\\na CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions\\nprovided by the bot. You will only need to do this once across all repos using our CLA.\\n\\nThis project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).\\nFor more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or\\ncontact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.\\n\\n\\n', 'role': 'assistant'}, {'content': \"The author of FLAML is Chi Wang, along with other collaborators including Qingyun Wu, Markus Weimer, Erkang Zhu, Silu Huang, Amin Saied, Susan Xueqing Liu, John Langford, Paul Mineiro, Marco Rossi, Moe Kayali, Shaokun Zhang, Feiran Jia, Yiran Wu, Hangyu Li, Yue Wang, Yin Tat Lee, Richard Peng, and Ahmed H. Awadallah, as indicated in the provided references for FLAML's research publications.\", 'role': 'user'}], summary=\"The author of FLAML is Chi Wang, along with other collaborators including Qingyun Wu, Markus Weimer, Erkang Zhu, Silu Huang, Amin Saied, Susan Xueqing Liu, John Langford, Paul Mineiro, Marco Rossi, Moe Kayali, Shaokun Zhang, Feiran Jia, Yiran Wu, Hangyu Li, Yue Wang, Yin Tat Lee, Richard Peng, and Ahmed H. Awadallah, as indicated in the provided references for FLAML's research publications.\", cost=({'total_cost': 0.11538, 'gpt-4': {'cost': 0.11538, 'prompt_tokens': 3632, 'completion_tokens': 107, 'total_tokens': 3739}}, {'total_cost': 0}), human_input=[])" + "ChatResult(chat_id=None, chat_history=[{'content': \"You're a retrieve augmented coding assistant. You answer user's questions based on your own knowledge and the\\ncontext provided by the user.\\nIf you can't answer the question with or without the current context, you should reply exactly `UPDATE CONTEXT`.\\nFor code generation, you must obey the following rules:\\nRule 1. You MUST NOT install any packages because all the packages needed are already installed.\\nRule 2. You must follow the formats below to write your code:\\n```language\\n# your code\\n```\\n\\nUser's question is: Who is the author of FLAML?\\n\\nContext is: # Research\\n\\nFor technical details, please check our research publications.\\n\\n- [FLAML: A Fast and Lightweight AutoML Library](https://www.microsoft.com/en-us/research/publication/flaml-a-fast-and-lightweight-automl-library/). Chi Wang, Qingyun Wu, Markus Weimer, Erkang Zhu. MLSys 2021.\\n\\n```bibtex\\n@inproceedings{wang2021flaml,\\n title={FLAML: A Fast and Lightweight AutoML Library},\\n author={Chi Wang and Qingyun Wu and Markus Weimer and Erkang Zhu},\\n year={2021},\\n booktitle={MLSys},\\n}\\n```\\n\\n- [Frugal Optimization for Cost-related Hyperparameters](https://arxiv.org/abs/2005.01571). Qingyun Wu, Chi Wang, Silu Huang. AAAI 2021.\\n\\n```bibtex\\n@inproceedings{wu2021cfo,\\n title={Frugal Optimization for Cost-related Hyperparameters},\\n author={Qingyun Wu and Chi Wang and Silu Huang},\\n year={2021},\\n booktitle={AAAI},\\n}\\n```\\n\\n- [Economical Hyperparameter Optimization With Blended Search Strategy](https://www.microsoft.com/en-us/research/publication/economical-hyperparameter-optimization-with-blended-search-strategy/). Chi Wang, Qingyun Wu, Silu Huang, Amin Saied. ICLR 2021.\\n\\n```bibtex\\n@inproceedings{wang2021blendsearch,\\n title={Economical Hyperparameter Optimization With Blended Search Strategy},\\n author={Chi Wang and Qingyun Wu and Silu Huang and Amin Saied},\\n year={2021},\\n booktitle={ICLR},\\n}\\n```\\n\\n- [An Empirical Study on Hyperparameter Optimization for Fine-Tuning Pre-trained Language Models](https://aclanthology.org/2021.acl-long.178.pdf). Susan Xueqing Liu, Chi Wang. ACL 2021.\\n\\n```bibtex\\n@inproceedings{liuwang2021hpolm,\\n title={An Empirical Study on Hyperparameter Optimization for Fine-Tuning Pre-trained Language Models},\\n author={Susan Xueqing Liu and Chi Wang},\\n year={2021},\\n booktitle={ACL},\\n}\\n```\\n\\n- [ChaCha for Online AutoML](https://www.microsoft.com/en-us/research/publication/chacha-for-online-automl/). Qingyun Wu, Chi Wang, John Langford, Paul Mineiro and Marco Rossi. ICML 2021.\\n\\n```bibtex\\n@inproceedings{wu2021chacha,\\n title={ChaCha for Online AutoML},\\n author={Qingyun Wu and Chi Wang and John Langford and Paul Mineiro and Marco Rossi},\\n year={2021},\\n booktitle={ICML},\\n}\\n```\\n\\n- [Fair AutoML](https://arxiv.org/abs/2111.06495). Qingyun Wu, Chi Wang. ArXiv preprint arXiv:2111.06495 (2021).\\n\\n```bibtex\\n@inproceedings{wuwang2021fairautoml,\\n title={Fair AutoML},\\n author={Qingyun Wu and Chi Wang},\\n year={2021},\\n booktitle={ArXiv preprint arXiv:2111.06495},\\n}\\n```\\n\\n- [Mining Robust Default Configurations for Resource-constrained AutoML](https://arxiv.org/abs/2202.09927). Moe Kayali, Chi Wang. ArXiv preprint arXiv:2202.09927 (2022).\\n\\n```bibtex\\n@inproceedings{kayaliwang2022default,\\n title={Mining Robust Default Configurations for Resource-constrained AutoML},\\n author={Moe Kayali and Chi Wang},\\n year={2022},\\n booktitle={ArXiv preprint arXiv:2202.09927},\\n}\\n```\\n\\n- [Targeted Hyperparameter Optimization with Lexicographic Preferences Over Multiple Objectives](https://openreview.net/forum?id=0Ij9_q567Ma). Shaokun Zhang, Feiran Jia, Chi Wang, Qingyun Wu. ICLR 2023 (notable-top-5%).\\n\\n```bibtex\\n@inproceedings{zhang2023targeted,\\n title={Targeted Hyperparameter Optimization with Lexicographic Preferences Over Multiple Objectives},\\n author={Shaokun Zhang and Feiran Jia and Chi Wang and Qingyun Wu},\\n booktitle={International Conference on Learning Representations},\\n year={2023},\\n url={https://openreview.net/forum?id=0Ij9_q567Ma},\\n}\\n```\\n\\n- [Cost-Effective Hyperparameter Optimization for Large Language Model Generation Inference](https://arxiv.org/abs/2303.04673). Chi Wang, Susan Xueqing Liu, Ahmed H. Awadallah. ArXiv preprint arXiv:2303.04673 (2023).\\n\\n```bibtex\\n@inproceedings{wang2023EcoOptiGen,\\n title={Cost-Effective Hyperparameter Optimization for Large Language Model Generation Inference},\\n author={Chi Wang and Susan Xueqing Liu and Ahmed H. Awadallah},\\n year={2023},\\n booktitle={ArXiv preprint arXiv:2303.04673},\\n}\\n```\\n\\n- [An Empirical Study on Challenging Math Problem Solving with GPT-4](https://arxiv.org/abs/2306.01337). Yiran Wu, Feiran Jia, Shaokun Zhang, Hangyu Li, Erkang Zhu, Yue Wang, Yin Tat Lee, Richard Peng, Qingyun Wu, Chi Wang. ArXiv preprint arXiv:2306.01337 (2023).\\n\\n```bibtex\\n@inproceedings{wu2023empirical,\\n title={An Empirical Study on Challenging Math Problem Solving with GPT-4},\\n author={Yiran Wu and Feiran Jia and Shaokun Zhang and Hangyu Li and Erkang Zhu and Yue Wang and Yin Tat Lee and Richard Peng and Qingyun Wu and Chi Wang},\\n year={2023},\\n booktitle={ArXiv preprint arXiv:2306.01337},\\n}\\n```\\n\\n\", 'role': 'assistant'}, {'content': 'The authors of FLAML are Chi Wang, Qingyun Wu, Markus Weimer, and Erkang Zhu.', 'role': 'user'}], summary='The authors of FLAML are Chi Wang, Qingyun Wu, Markus Weimer, and Erkang Zhu.', cost=({'total_cost': 0.04596, 'gpt-4': {'cost': 0.04596, 'prompt_tokens': 1486, 'completion_tokens': 23, 'total_tokens': 1509}}, {'total_cost': 0.04596, 'gpt-4': {'cost': 0.04596, 'prompt_tokens': 1486, 'completion_tokens': 23, 'total_tokens': 1509}}), human_input=[])" ] }, - "execution_count": 18, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } @@ -1031,8 +1004,10 @@ ], "metadata": { "front_matter": { - "tags": ["rag"], - "description": "This notebook demonstrates the usage of QdrantRetrieveUserProxyAgent for RAG." + "description": "This notebook demonstrates the usage of QdrantRetrieveUserProxyAgent for RAG.", + "tags": [ + "rag" + ] }, "kernelspec": { "display_name": "Python 3 (ipykernel)", diff --git a/notebook/agentchat_society_of_mind.ipynb b/notebook/agentchat_society_of_mind.ipynb index 9a456ccdab9..79e5990a2af 100644 --- a/notebook/agentchat_society_of_mind.ipynb +++ b/notebook/agentchat_society_of_mind.ipynb @@ -357,7 +357,7 @@ ], "metadata": { "front_matter": { - "tags": ["orchestration"], + "tags": ["orchestration", "nested chat"], "description": "Explore the demonstration of the SocietyOfMindAgent in the AutoGen library, which runs a group chat as an internal monologue, but appears to the external world as a single agent, offering a structured way to manage complex interactions among multiple agents and handle issues such as extracting responses from complex dialogues and dealing with context window constraints." }, "kernelspec": { diff --git a/notebook/agentchat_sql_spider.ipynb b/notebook/agentchat_sql_spider.ipynb index 0c850b7e082..e2747100601 100644 --- a/notebook/agentchat_sql_spider.ipynb +++ b/notebook/agentchat_sql_spider.ipynb @@ -45,12 +45,13 @@ ], "source": [ "# %pip install spider-env\n", - "from spider_env import SpiderEnv\n", - "\n", - "from autogen import UserProxyAgent, ConversableAgent, config_list_from_json\n", - "from typing import Annotated, Dict\n", "import json\n", "import os\n", + "from typing import Annotated, Dict\n", + "\n", + "from spider_env import SpiderEnv\n", + "\n", + "from autogen import ConversableAgent, UserProxyAgent, config_list_from_json\n", "\n", "gym = SpiderEnv()\n", "\n", diff --git a/notebook/agentchat_surfer.ipynb b/notebook/agentchat_surfer.ipynb index dcbe6ee66bc..b6a8c3327b7 100644 --- a/notebook/agentchat_surfer.ipynb +++ b/notebook/agentchat_surfer.ipynb @@ -152,7 +152,7 @@ "metadata": {}, "source": [ "### Example 1: Search, summarize\n", - "- Search for information aobut Microsoft AutoGen\n", + "- Search for information about Microsoft AutoGen\n", "- Summarize the results\n", "- Visit the Getting Started Docs page" ] diff --git a/notebook/agentchat_transform_messages.ipynb b/notebook/agentchat_transform_messages.ipynb new file mode 100644 index 00000000000..d0216e05dd2 --- /dev/null +++ b/notebook/agentchat_transform_messages.ipynb @@ -0,0 +1,588 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "b7549a27-bc4a-4609-bb25-cc7d95cf8c23", + "metadata": {}, + "source": [ + "# Preprocessing Chat History with `TransformMessages`\n", + "\n", + "## Introduction\n", + "This notebook illustrates how to use `TransformMessages` give any `ConversableAgent` the ability to handle long contexts, sensitive data, and more.\n", + "\n", + "````{=mdx}\n", + ":::info Requirements\n", + "Install `pyautogen`:\n", + "```bash\n", + "pip install pyautogen\n", + "```\n", + "\n", + "For more information, please refer to the [installation guide](/docs/installation/).\n", + ":::\n", + "````" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "47773f79-c0fd-4993-bc6e-3d1a57690118", + "metadata": {}, + "outputs": [], + "source": [ + "import copy\n", + "import pprint\n", + "import re\n", + "from typing import Dict, List, Tuple\n", + "\n", + "import autogen\n", + "from autogen.agentchat.contrib.capabilities import transform_messages, transforms" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "9f09246b-a7d0-4238-b62c-1e72c7d815b3", + "metadata": {}, + "outputs": [], + "source": [ + "config_list = autogen.config_list_from_json(\n", + " env_or_file=\"OAI_CONFIG_LIST\",\n", + ")\n", + "# Define your llm config\n", + "llm_config = {\"config_list\": config_list}" + ] + }, + { + "cell_type": "markdown", + "id": "ea68962a-048d-42e9-9fca-cd944c56184d", + "metadata": {}, + "source": [ + "````{=mdx}\n", + ":::tip\n", + "Learn more about configuring LLMs for agents [here](/docs/topics/llm_configuration).\n", + ":::\n", + "````" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "84d0e5ad-8b35-4b30-847e-4723e9c76f7c", + "metadata": {}, + "outputs": [], + "source": [ + "# Define your agent; the user proxy and an assistant\n", + "assistant = autogen.AssistantAgent(\n", + " \"assistant\",\n", + " llm_config=llm_config,\n", + ")\n", + "user_proxy = autogen.UserProxyAgent(\n", + " \"user_proxy\",\n", + " human_input_mode=\"NEVER\",\n", + " is_termination_msg=lambda x: \"TERMINATE\" in x.get(\"content\", \"\"),\n", + " max_consecutive_auto_reply=10,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "180aa953-45be-469a-a94f-0ed0b4ef5ddf", + "metadata": {}, + "source": [ + "## Handling Long Contexts\n", + "\n", + "Imagine a scenario where the LLM generates an extensive amount of text, surpassing the token limit imposed by your API provider. To address this issue, you can leverage `TransformMessages` along with its constituent transformations, `MessageHistoryLimiter` and `MessageTokenLimiter`.\n", + "\n", + "- `MessageHistoryLimiter`: You can restrict the total number of messages considered as context history. This transform is particularly useful when you want to limit the conversational context to a specific number of recent messages, ensuring efficient processing and response generation.\n", + "- `MessageTokenLimiter`: Enables you to cap the total number of tokens, either on a per-message basis or across the entire context history (or both). This transformation is invaluable when you need to adhere to strict token limits imposed by your API provider, preventing unnecessary costs or errors caused by exceeding the allowed token count. Additionally, a `min_tokens` threshold can be applied, ensuring that the transformation is only applied when the number of tokens is not less than the specified threshold." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "34b943a2-ec58-41bc-a449-d9118c4bbdea", + "metadata": {}, + "outputs": [], + "source": [ + "# Limit the message history to the 3 most recent messages\n", + "max_msg_transfrom = transforms.MessageHistoryLimiter(max_messages=3)\n", + "\n", + "# Limit the token limit per message to 10 tokens\n", + "token_limit_transform = transforms.MessageTokenLimiter(max_tokens_per_message=3, min_tokens=10)" + ] + }, + { + "cell_type": "markdown", + "id": "679c1026-4e1b-4c07-85cc-86594cc0b87b", + "metadata": {}, + "source": [ + "## Example 1: Limiting number of messages\n", + "Let's take a look at how these transformations will effect the messages. Below we see that by applying the `MessageHistoryLimiter`, we can see that we limited the context history to the 3 most recent messages." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "61a2ead4-5f8b-4108-b1f0-3b51b41e2231", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[{'content': 'how', 'role': 'user'},\n", + " {'content': [{'text': 'are you doing?', 'type': 'text'}], 'role': 'assistant'},\n", + " {'content': 'very very very very very very long string', 'role': 'user'}]\n" + ] + } + ], + "source": [ + "messages = [\n", + " {\"role\": \"user\", \"content\": \"hello\"},\n", + " {\"role\": \"assistant\", \"content\": [{\"type\": \"text\", \"text\": \"there\"}]},\n", + " {\"role\": \"user\", \"content\": \"how\"},\n", + " {\"role\": \"assistant\", \"content\": [{\"type\": \"text\", \"text\": \"are you doing?\"}]},\n", + " {\"role\": \"user\", \"content\": \"very very very very very very long string\"},\n", + "]\n", + "\n", + "processed_messages = max_msg_transfrom.apply_transform(copy.deepcopy(messages))\n", + "pprint.pprint(processed_messages)" + ] + }, + { + "cell_type": "markdown", + "id": "610739af-b812-404e-82d2-b3ed796b8b6c", + "metadata": {}, + "source": [ + "## Example 2: Limiting number of tokens\n", + "\n", + "Now let's test limiting the number of tokens in messages. We can see that we can limit the number of tokens to 3, which is equivalent to 3 words in this instance." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "739dd260-fa95-4e5d-ae84-9cb7f40de975", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[{'content': 'hello', 'role': 'user'},\n", + " {'content': [{'text': 'there', 'type': 'text'}], 'role': 'assistant'},\n", + " {'content': 'how', 'role': 'user'},\n", + " {'content': [{'text': 'are you doing', 'type': 'text'}], 'role': 'assistant'},\n", + " {'content': 'very very very', 'role': 'user'}]\n" + ] + } + ], + "source": [ + "processed_messages = token_limit_transform.apply_transform(copy.deepcopy(messages))\n", + "\n", + "pprint.pprint(processed_messages)" + ] + }, + { + "cell_type": "markdown", + "id": "86a98e08", + "metadata": {}, + "source": [ + "Also, the `min_tokens` threshold is set to 10, indicating that the transformation will not be applied if the total number of tokens in the messages is less than that. This is especially beneficial when the transformation should only occur after a certain number of tokens has been reached, such as in the context window of the model. An example is provided below." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "05c42ffc", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[{'content': 'hello there, how are you?', 'role': 'user'},\n", + " {'content': [{'text': 'hello', 'type': 'text'}], 'role': 'assistant'}]\n" + ] + } + ], + "source": [ + "short_messages = [\n", + " {\"role\": \"user\", \"content\": \"hello there, how are you?\"},\n", + " {\"role\": \"assistant\", \"content\": [{\"type\": \"text\", \"text\": \"hello\"}]},\n", + "]\n", + "\n", + "processed_short_messages = token_limit_transform.apply_transform(copy.deepcopy(short_messages))\n", + "\n", + "pprint.pprint(processed_short_messages)" + ] + }, + { + "cell_type": "markdown", + "id": "35fa2844-bd83-42ac-8275-959f093b7bc7", + "metadata": {}, + "source": [ + "## Example 3: Combining transformations\n", + "\n", + "Let's test these transforms with agents (the upcoming test is replicated from the agentchat_capability_long_context_handling notebook). We will see that the agent without the capability to handle long context will result in an error, while the agent with that capability will have no issues." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "80e53623-2830-41b7-8ae2-bf3668071657", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33muser_proxy\u001b[0m (to assistant):\n", + "\n", + "plot and save a graph of x^2 from -10 to 10\n", + "\n", + "--------------------------------------------------------------------------------\n", + "Encountered an error with the base assistant\n", + "Error code: 400 - {'error': {'message': \"This model's maximum context length is 16385 tokens. However, your messages resulted in 1009487 tokens. Please reduce the length of the messages.\", 'type': 'invalid_request_error', 'param': 'messages', 'code': 'context_length_exceeded'}}\n", + "\n", + "\n", + "\n", + "\u001b[33muser_proxy\u001b[0m (to assistant):\n", + "\n", + "plot and save a graph of x^2 from -10 to 10\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mRemoved 1991 messages. Number of messages reduced from 2001 to 10.\u001b[0m\n", + "\u001b[33mTruncated 3804 tokens. Number of tokens reduced from 4019 to 215\u001b[0m\n", + "\u001b[33massistant\u001b[0m (to user_proxy):\n", + "\n", + "```python\n", + "# filename: plot_x_squared.py\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "\n", + "# Generate an array of x values from -10 to 10\n", + "x = np.linspace(-10, 10, 400)\n", + "# Calculate the y values by squaring the x values\n", + "y = x**2\n", + "\n", + "# Create the plot\n", + "plt.figure()\n", + "plt.plot(x, y)\n", + "\n", + "# Title and labels\n", + "plt.title('Graph of y = x^2')\n", + "plt.xlabel('x')\n", + "plt.ylabel('y')\n", + "\n", + "# Save the plot as a file\n", + "plt.savefig('x_squared_plot.png')\n", + "\n", + "# Show the plot\n", + "plt.show()\n", + "```\n", + "\n", + "Please save the above code into a file named `plot_x_squared.py`. After saving the code, you can execute it to generate and save the graph of y = x^2 from -10 to 10. The graph will also be displayed to you and the file `x_squared_plot.png` will be created in the current directory. Make sure you have `matplotlib` and `numpy` libraries installed in your Python environment before executing the code. If they are not installed, you can install them using `pip`:\n", + "\n", + "```sh\n", + "pip install matplotlib numpy\n", + "```\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[31m\n", + ">>>>>>>> EXECUTING CODE BLOCK 0 (inferred language is python)...\u001b[0m\n", + "\u001b[31m\n", + ">>>>>>>> EXECUTING CODE BLOCK 1 (inferred language is sh)...\u001b[0m\n", + "\u001b[33muser_proxy\u001b[0m (to assistant):\n", + "\n", + "exitcode: 0 (execution succeeded)\n", + "Code output: \n", + "Figure(640x480)\n", + "\n", + "Requirement already satisfied: matplotlib in c:\\users\\bt314mc\\appdata\\local\\programs\\python\\python311\\lib\\site-packages (3.8.0)\n", + "Requirement already satisfied: numpy in c:\\users\\bt314mc\\appdata\\local\\programs\\python\\python311\\lib\\site-packages (1.26.0)\n", + "Requirement already satisfied: contourpy>=1.0.1 in c:\\users\\bt314mc\\appdata\\local\\programs\\python\\python311\\lib\\site-packages (from matplotlib) (1.1.1)\n", + "Requirement already satisfied: cycler>=0.10 in c:\\users\\bt314mc\\appdata\\local\\programs\\python\\python311\\lib\\site-packages (from matplotlib) (0.11.0)\n", + "Requirement already satisfied: fonttools>=4.22.0 in c:\\users\\bt314mc\\appdata\\local\\programs\\python\\python311\\lib\\site-packages (from matplotlib) (4.42.1)\n", + "Requirement already satisfied: kiwisolver>=1.0.1 in c:\\users\\bt314mc\\appdata\\local\\programs\\python\\python311\\lib\\site-packages (from matplotlib) (1.4.5)\n", + "Requirement already satisfied: packaging>=20.0 in c:\\users\\bt314mc\\appdata\\local\\programs\\python\\python311\\lib\\site-packages (from matplotlib) (23.2)\n", + "Requirement already satisfied: pillow>=6.2.0 in c:\\users\\bt314mc\\appdata\\local\\programs\\python\\python311\\lib\\site-packages (from matplotlib) (10.0.1)\n", + "Requirement already satisfied: pyparsing>=2.3.1 in c:\\users\\bt314mc\\appdata\\local\\programs\\python\\python311\\lib\\site-packages (from matplotlib) (3.1.1)\n", + "Requirement already satisfied: python-dateutil>=2.7 in c:\\users\\bt314mc\\appdata\\local\\programs\\python\\python311\\lib\\site-packages (from matplotlib) (2.8.2)\n", + "Requirement already satisfied: six>=1.5 in c:\\users\\bt314mc\\appdata\\local\\programs\\python\\python311\\lib\\site-packages (from python-dateutil>=2.7->matplotlib) (1.16.0)\n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mRemoved 1993 messages. Number of messages reduced from 2003 to 10.\u001b[0m\n", + "\u001b[33mTruncated 3523 tokens. Number of tokens reduced from 3788 to 265\u001b[0m\n", + "\u001b[33massistant\u001b[0m (to user_proxy):\n", + "\n", + "It appears that the matplotlib library is already installed on your system, and the previous script started successfully but did not finish because the plotting code was incomplete.\n", + "\n", + "I will provide you with the full code to plot and save the graph of \\( x^2 \\) from -10 to 10.\n", + "\n", + "```python\n", + "# filename: plot_x_squared.py\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "\n", + "# Generate an array of x values from -10 to 10\n", + "x = np.linspace(-10, 10, 400)\n", + "# Calculate the y values based on the x values\n", + "y = x**2\n", + "\n", + "# Create the plot\n", + "plt.figure(figsize=(8, 6))\n", + "plt.plot(x, y, label='y = x^2')\n", + "\n", + "# Add a title and labels\n", + "plt.title('Plot of y = x^2')\n", + "plt.xlabel('x')\n", + "plt.ylabel('y')\n", + "\n", + "# Add a legend\n", + "plt.legend()\n", + "\n", + "# Save the figure\n", + "plt.savefig('plot_x_squared.png')\n", + "\n", + "# Show the plot\n", + "plt.show()\n", + "```\n", + "\n", + "Please execute this Python code in its entirety. It will create a graph of \\( y = x^2 \\) with x values ranging from -10 to 10, and then it will save the graph as a PNG file named 'plot_x_squared.png' in the current working directory. It will also display the plot window with the graph.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[31m\n", + ">>>>>>>> EXECUTING CODE BLOCK 0 (inferred language is python)...\u001b[0m\n", + "\u001b[33muser_proxy\u001b[0m (to assistant):\n", + "\n", + "exitcode: 0 (execution succeeded)\n", + "Code output: \n", + "Figure(800x600)\n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mRemoved 1995 messages. Number of messages reduced from 2005 to 10.\u001b[0m\n", + "\u001b[33mTruncated 2802 tokens. Number of tokens reduced from 3086 to 284\u001b[0m\n", + "\u001b[33massistant\u001b[0m (to user_proxy):\n", + "\n", + "It seems the graph has been generated, but the output doesn't tell us if the graph was saved. The expected behavior was to have a file saved in the current working directory. Can you please check in your current directory for a file named `plot_x_squared.png`? If it exists, then the task is complete.\n", + "\n", + "If you don't find the file, let me know, and I will troubleshoot further.\n", + "\n", + "--------------------------------------------------------------------------------\n" + ] + } + ], + "source": [ + "assistant_base = autogen.AssistantAgent(\n", + " \"assistant\",\n", + " llm_config=llm_config,\n", + ")\n", + "\n", + "assistant_with_context_handling = autogen.AssistantAgent(\n", + " \"assistant\",\n", + " llm_config=llm_config,\n", + ")\n", + "# suppose this capability is not available\n", + "context_handling = transform_messages.TransformMessages(\n", + " transforms=[\n", + " transforms.MessageHistoryLimiter(max_messages=10),\n", + " transforms.MessageTokenLimiter(max_tokens=1000, max_tokens_per_message=50, min_tokens=500),\n", + " ]\n", + ")\n", + "\n", + "context_handling.add_to_agent(assistant_with_context_handling)\n", + "\n", + "user_proxy = autogen.UserProxyAgent(\n", + " \"user_proxy\",\n", + " human_input_mode=\"NEVER\",\n", + " is_termination_msg=lambda x: \"TERMINATE\" in x.get(\"content\", \"\"),\n", + " code_execution_config={\n", + " \"work_dir\": \"coding\",\n", + " \"use_docker\": False,\n", + " },\n", + " max_consecutive_auto_reply=2,\n", + ")\n", + "\n", + "# suppose the chat history is large\n", + "# Create a very long chat history that is bound to cause a crash\n", + "# for gpt 3.5\n", + "for i in range(1000):\n", + " # define a fake, very long messages\n", + " assitant_msg = {\"role\": \"assistant\", \"content\": \"test \" * 1000}\n", + " user_msg = {\"role\": \"user\", \"content\": \"\"}\n", + "\n", + " assistant_base.send(assitant_msg, user_proxy, request_reply=False, silent=True)\n", + " assistant_with_context_handling.send(assitant_msg, user_proxy, request_reply=False, silent=True)\n", + " user_proxy.send(user_msg, assistant_base, request_reply=False, silent=True)\n", + " user_proxy.send(user_msg, assistant_with_context_handling, request_reply=False, silent=True)\n", + "\n", + "try:\n", + " user_proxy.initiate_chat(assistant_base, message=\"plot and save a graph of x^2 from -10 to 10\", clear_history=False)\n", + "except Exception as e:\n", + " print(\"Encountered an error with the base assistant\")\n", + " print(e)\n", + " print(\"\\n\\n\")\n", + "\n", + "try:\n", + " user_proxy.initiate_chat(\n", + " assistant_with_context_handling, message=\"plot and save a graph of x^2 from -10 to 10\", clear_history=False\n", + " )\n", + "except Exception as e:\n", + " print(e)" + ] + }, + { + "cell_type": "markdown", + "id": "5e380678-a923-43cb-91b1-f9c9e8deede2", + "metadata": {}, + "source": [ + "## Handling Sensitive Data\n", + "\n", + "You can use the `MessageTransform` protocol to create custom message transformations that redact sensitive data from the chat history. This is particularly useful when you want to ensure that sensitive information, such as API keys, passwords, or personal data, is not exposed in the chat history or logs.\n", + "\n", + "Now, we will create a custom message transform to detect any OpenAI API key and redact it." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "74429344-3c0a-4057-aba3-27358fbf059c", + "metadata": {}, + "outputs": [], + "source": [ + "# The transform must adhere to transform_messages.MessageTransform protocol.\n", + "class MessageRedact:\n", + " def __init__(self):\n", + " self._openai_key_pattern = r\"sk-([a-zA-Z0-9]{48})\"\n", + " self._replacement_string = \"REDACTED\"\n", + "\n", + " def apply_transform(self, messages: List[Dict]) -> List[Dict]:\n", + " temp_messages = copy.deepcopy(messages)\n", + "\n", + " for message in temp_messages:\n", + " if isinstance(message[\"content\"], str):\n", + " message[\"content\"] = re.sub(self._openai_key_pattern, self._replacement_string, message[\"content\"])\n", + " elif isinstance(message[\"content\"], list):\n", + " for item in message[\"content\"]:\n", + " if item[\"type\"] == \"text\":\n", + " item[\"text\"] = re.sub(self._openai_key_pattern, self._replacement_string, item[\"text\"])\n", + " return temp_messages\n", + "\n", + " def get_logs(self, pre_transform_messages: List[Dict], post_transform_messages: List[Dict]) -> Tuple[str, bool]:\n", + " keys_redacted = self._count_redacted(post_transform_messages) - self._count_redacted(pre_transform_messages)\n", + " if keys_redacted > 0:\n", + " return f\"Redacted {keys_redacted} OpenAI API keys.\", True\n", + " return \"\", False\n", + "\n", + " def _count_redacted(self, messages: List[Dict]) -> int:\n", + " # counts occurrences of \"REDACTED\" in message content\n", + " count = 0\n", + " for message in messages:\n", + " if isinstance(message[\"content\"], str):\n", + " if \"REDACTED\" in message[\"content\"]:\n", + " count += 1\n", + " elif isinstance(message[\"content\"], list):\n", + " for item in message[\"content\"]:\n", + " if isinstance(item, dict) and \"text\" in item:\n", + " if \"REDACTED\" in item[\"text\"]:\n", + " count += 1\n", + " return count" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "8a79c0b4-5ff8-49c5-b8a6-c54ca4c7cca2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33muser_proxy\u001b[0m (to assistant):\n", + "\n", + "What are the two API keys that I just provided\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mRedacted 2 OpenAI API keys.\u001b[0m\n", + "\u001b[33massistant\u001b[0m (to user_proxy):\n", + "\n", + "As an AI, I must inform you that it is not safe to share API keys publicly as they can be used to access your private data or services that can incur costs. Given that you've typed \"REDACTED\" instead of the actual keys, it seems you are aware of the privacy concerns and are likely testing my response or simulating an exchange without exposing real credentials, which is a good practice for privacy and security reasons.\n", + "\n", + "To respond directly to your direct question: The two API keys you provided are both placeholders indicated by the text \"REDACTED\", and not actual API keys. If these were real keys, I would have reiterated the importance of keeping them secure and would not display them here.\n", + "\n", + "Remember to keep your actual API keys confidential to prevent unauthorized use. If you've accidentally exposed real API keys, you should revoke or regenerate them as soon as possible through the corresponding service's API management console.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33muser_proxy\u001b[0m (to assistant):\n", + "\n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mRedacted 2 OpenAI API keys.\u001b[0m\n" + ] + } + ], + "source": [ + "assistant_with_redact = autogen.AssistantAgent(\n", + " \"assistant\",\n", + " llm_config=llm_config,\n", + " max_consecutive_auto_reply=1,\n", + ")\n", + "# suppose this capability is not available\n", + "redact_handling = transform_messages.TransformMessages(transforms=[MessageRedact()])\n", + "\n", + "redact_handling.add_to_agent(assistant_with_redact)\n", + "\n", + "user_proxy = autogen.UserProxyAgent(\n", + " \"user_proxy\",\n", + " human_input_mode=\"NEVER\",\n", + " max_consecutive_auto_reply=1,\n", + ")\n", + "\n", + "messages = [\n", + " {\"content\": \"api key 1 = sk-7nwt00xv6fuegfu3gnwmhrgxvuc1cyrhxcq1quur9zvf05fy\"}, # Don't worry, randomly generated\n", + " {\"content\": [{\"type\": \"text\", \"text\": \"API key 2 = sk-9wi0gf1j2rz6utaqd3ww3o6c1h1n28wviypk7bd81wlj95an\"}]},\n", + "]\n", + "\n", + "for message in messages:\n", + " user_proxy.send(message, assistant_with_redact, request_reply=False, silent=True)\n", + "\n", + "result = user_proxy.initiate_chat(\n", + " assistant_with_redact, message=\"What are the two API keys that I just provided\", clear_history=False\n", + ")" + ] + } + ], + "metadata": { + "front_matter": { + "description": "Preprocessing chat history with `TransformMessages`", + "tags": [ + "long context handling", + "capability" + ] + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebook/agentchat_video_transcript_translate_with_whisper.ipynb b/notebook/agentchat_video_transcript_translate_with_whisper.ipynb index be955d5d74b..48812ad01a6 100644 --- a/notebook/agentchat_video_transcript_translate_with_whisper.ipynb +++ b/notebook/agentchat_video_transcript_translate_with_whisper.ipynb @@ -84,8 +84,8 @@ "\n", "import whisper\n", "from openai import OpenAI\n", - "import autogen\n", "\n", + "import autogen\n", "\n", "source_language = \"English\"\n", "target_language = \"Chinese\"\n", diff --git a/notebook/agentchat_webscraping_with_apify.ipynb b/notebook/agentchat_webscraping_with_apify.ipynb new file mode 100644 index 00000000000..0429c10f8a7 --- /dev/null +++ b/notebook/agentchat_webscraping_with_apify.ipynb @@ -0,0 +1,389 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Web Scraping using Apify Tools\n", + "\n", + "This notebook shows how to use Apify tools with AutoGen agents to\n", + "scrape data from a website and formate the output." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First we need to install the Apify SDK and the AutoGen library." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "! pip install -qqq pyautogen apify-client" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Setting up the LLM configuration and the Apify API key is also required." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "\n", + "config_list = [\n", + " {\"model\": \"gpt-4\", \"api_key\": os.getenv(\"OPENAI_API_KEY\")},\n", + "]\n", + "\n", + "apify_api_key = os.getenv(\"APIFY_API_KEY\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's define the tool for scraping data from the website using Apify actor.\n", + "Read more about tool use in this [tutorial chapter](/docs/tutorial/tool-use)." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "from apify_client import ApifyClient\n", + "from typing_extensions import Annotated\n", + "\n", + "\n", + "def scrape_page(url: Annotated[str, \"The URL of the web page to scrape\"]) -> Annotated[str, \"Scraped content\"]:\n", + " # Initialize the ApifyClient with your API token\n", + " client = ApifyClient(token=apify_api_key)\n", + "\n", + " # Prepare the Actor input\n", + " run_input = {\n", + " \"startUrls\": [{\"url\": url}],\n", + " \"useSitemaps\": False,\n", + " \"crawlerType\": \"playwright:firefox\",\n", + " \"includeUrlGlobs\": [],\n", + " \"excludeUrlGlobs\": [],\n", + " \"ignoreCanonicalUrl\": False,\n", + " \"maxCrawlDepth\": 0,\n", + " \"maxCrawlPages\": 1,\n", + " \"initialConcurrency\": 0,\n", + " \"maxConcurrency\": 200,\n", + " \"initialCookies\": [],\n", + " \"proxyConfiguration\": {\"useApifyProxy\": True},\n", + " \"maxSessionRotations\": 10,\n", + " \"maxRequestRetries\": 5,\n", + " \"requestTimeoutSecs\": 60,\n", + " \"dynamicContentWaitSecs\": 10,\n", + " \"maxScrollHeightPixels\": 5000,\n", + " \"removeElementsCssSelector\": \"\"\"nav, footer, script, style, noscript, svg,\n", + " [role=\\\"alert\\\"],\n", + " [role=\\\"banner\\\"],\n", + " [role=\\\"dialog\\\"],\n", + " [role=\\\"alertdialog\\\"],\n", + " [role=\\\"region\\\"][aria-label*=\\\"skip\\\" i],\n", + " [aria-modal=\\\"true\\\"]\"\"\",\n", + " \"removeCookieWarnings\": True,\n", + " \"clickElementsCssSelector\": '[aria-expanded=\"false\"]',\n", + " \"htmlTransformer\": \"readableText\",\n", + " \"readableTextCharThreshold\": 100,\n", + " \"aggressivePrune\": False,\n", + " \"debugMode\": True,\n", + " \"debugLog\": True,\n", + " \"saveHtml\": True,\n", + " \"saveMarkdown\": True,\n", + " \"saveFiles\": False,\n", + " \"saveScreenshots\": False,\n", + " \"maxResults\": 9999999,\n", + " \"clientSideMinChangePercentage\": 15,\n", + " \"renderingTypeDetectionPercentage\": 10,\n", + " }\n", + "\n", + " # Run the Actor and wait for it to finish\n", + " run = client.actor(\"aYG0l9s7dbB7j3gbS\").call(run_input=run_input)\n", + "\n", + " # Fetch and print Actor results from the run's dataset (if there are any)\n", + " text_data = \"\"\n", + " for item in client.dataset(run[\"defaultDatasetId\"]).iterate_items():\n", + " text_data += item.get(\"text\", \"\") + \"\\n\"\n", + "\n", + " average_token = 0.75\n", + " max_tokens = 20000 # slightly less than max to be safe 32k\n", + " text_data = text_data[: int(average_token * max_tokens)]\n", + " return text_data" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Create the agents and register the tool." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": {}, + "outputs": [], + "source": [ + "from autogen import ConversableAgent, register_function\n", + "\n", + "# Create web scrapper agent.\n", + "scraper_agent = ConversableAgent(\n", + " \"WebScraper\",\n", + " llm_config={\"config_list\": config_list},\n", + " system_message=\"You are a web scrapper and you can scrape any web page using the tools provided. \"\n", + " \"Returns 'TERMINATE' when the scraping is done.\",\n", + ")\n", + "\n", + "# Create user proxy agent.\n", + "user_proxy_agent = ConversableAgent(\n", + " \"UserProxy\",\n", + " llm_config=False, # No LLM for this agent.\n", + " human_input_mode=\"NEVER\",\n", + " code_execution_config=False, # No code execution for this agent.\n", + " is_termination_msg=lambda x: x.get(\"content\", \"\") is not None and \"terminate\" in x[\"content\"].lower(),\n", + " default_auto_reply=\"Please continue if not finished, otherwise return 'TERMINATE'.\",\n", + ")\n", + "\n", + "# Register the function with the agents.\n", + "register_function(\n", + " scrape_page,\n", + " caller=scraper_agent,\n", + " executor=user_proxy_agent,\n", + " name=\"scrape_page\",\n", + " description=\"Scrape a web page and return the content.\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Start the conversation for scraping web data. We used the\n", + "`reflection_with_llm` option for summary method\n", + "to perform the formatting of the output into a desired format.\n", + "The summary method is called after the conversation is completed\n", + "given the complete history of the conversation." + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mUserProxy\u001b[0m (to WebScraper):\n", + "\n", + "Can you scrape agentops.ai for me?\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[31m\n", + ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", + "\u001b[33mWebScraper\u001b[0m (to UserProxy):\n", + "\n", + "\u001b[32m***** Suggested tool call (call_0qok2jvCxOfv7HOA0oxPWneM): scrape_page *****\u001b[0m\n", + "Arguments: \n", + "{\n", + "\"url\": \"https://www.agentops.ai\"\n", + "}\n", + "\u001b[32m****************************************************************************\u001b[0m\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[35m\n", + ">>>>>>>> EXECUTING FUNCTION scrape_page...\u001b[0m\n", + "\u001b[33mUserProxy\u001b[0m (to WebScraper):\n", + "\n", + "\u001b[33mUserProxy\u001b[0m (to WebScraper):\n", + "\n", + "\u001b[32m***** Response from calling tool (call_0qok2jvCxOfv7HOA0oxPWneM) *****\u001b[0m\n", + "START NOW\n", + "Take your business to the next level with our features \n", + "AI Agents Suck.\n", + "We're Fixing That. \n", + "Build compliant AI agents with observability, evals, and replay analytics. No more black boxes and prompt guessing.\n", + "New! Introducing AgentOps\n", + "Three Lines of Code. Unlimited Testing. \n", + "Instant Testing + Debugging = Compliant AI Agents That Work\n", + "5\n", + "# Beginning of program's code (i.e. main.py, __init__.py)\n", + "6\n", + "ao_client = agentops.Client()\n", + "9\n", + "# (optional: record specific functions)\n", + "10\n", + "@ao_client.record_action('sample function being record')\n", + "11\n", + "def sample_function(...):\n", + "15\n", + "ao_client.end_session('Success')\n", + "Prototype to Production\n", + "Generous free limits, upgrade only when you need it.\n", + "\n", + "\u001b[32m**********************************************************************\u001b[0m\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[31m\n", + ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", + "\u001b[33mWebScraper\u001b[0m (to UserProxy):\n", + "\n", + "Sure, here's the information from the website agentops.ai:\n", + "\n", + "- Their main value proposition is to fix bad AI Agents and replace black boxes and prompt guessing with compliant, observable AI agents that come with evals and replay analytics.\n", + "- Their latest product is AgentOps. The simple and instant testing & debugging offered promises better-performing compliant AI agents.\n", + "- Integration is easy with just three lines of code.\n", + "- They let you record specific functions.\n", + "- They provide generous free limits and you only need to upgrade when necessary.\n", + "\n", + "Here's a sample of their code:\n", + "```python\n", + "ao_client = agentops.Client()\n", + "\n", + "# optional: record specific functions\n", + "@ao_client.record_action('sample function being record')\n", + "def sample_function(...):\n", + " ...\n", + "\n", + "ao_client.end_session('Success')\n", + "```\n", + "This code is for sample usage of their libraries/functions.\n", + "\n", + "Let me know if you need more specific details.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mUserProxy\u001b[0m (to WebScraper):\n", + "\n", + "Please continue if not finished, otherwise return 'TERMINATE'.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[31m\n", + ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", + "\u001b[33mWebScraper\u001b[0m (to UserProxy):\n", + "\n", + "TERMINATE\n", + "\n", + "--------------------------------------------------------------------------------\n" + ] + } + ], + "source": [ + "chat_result = user_proxy_agent.initiate_chat(\n", + " scraper_agent,\n", + " message=\"Can you scrape agentops.ai for me?\",\n", + " summary_method=\"reflection_with_llm\",\n", + " summary_args={\n", + " \"summary_prompt\": \"\"\"Summarize the scraped content and format summary EXACTLY as follows:\n", + "---\n", + "*Company name*:\n", + "`Acme Corp`\n", + "---\n", + "*Website*:\n", + "`acmecorp.com`\n", + "---\n", + "*Description*:\n", + "`Company that does things.`\n", + "---\n", + "*Tags*:\n", + "`Manufacturing. Retail. E-commerce.`\n", + "---\n", + "*Takeaways*:\n", + "`Provides shareholders with value by selling products.`\n", + "---\n", + "*Questions*:\n", + "`What products do they sell? How do they make money? What is their market share?`\n", + "---\n", + "\"\"\"\n", + " },\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The output is stored in the summary." + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "---\n", + "*Company name*:\n", + "`AgentOps`\n", + "---\n", + "*Website*:\n", + "`agentops.ai`\n", + "---\n", + "*Description*:\n", + "`Company that aims to improve AI agents. They offer observed and evaluable AI agents with replay analytics as an alternative to black box models and blind prompting.`\n", + "---\n", + "*Tags*:\n", + "`Artificial Intelligence, AI agents, Observability, Analytics.`\n", + "---\n", + "*Takeaways*:\n", + "`Their product, AgentOps, allows for easy and instant testing and debugging of AI agents. Integration is as simple as writing three lines of code. They also provide generous free limits and mandate upgrades only when necessary.`\n", + "---\n", + "*Questions*:\n", + "`What differentiates AgentOps from other, similar products? How does their pricing scale with usage? What are the details of their \"generous free limits\"?`\n", + "---\n" + ] + } + ], + "source": [ + "print(chat_result.summary)" + ] + } + ], + "metadata": { + "front_matter": { + "description": "Scrapping web pages and summarizing the content using agents with tools.", + "tags": [ + "web scraping", + "apify", + "tool use" + ], + "title": "Web Scraper Agent using Apify Tools" + }, + "kernelspec": { + "display_name": "autogen", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.5" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/notebook/agentchat_websockets.ipynb b/notebook/agentchat_websockets.ipynb index 7bf03f786de..7e6e449675c 100644 --- a/notebook/agentchat_websockets.ipynb +++ b/notebook/agentchat_websockets.ipynb @@ -52,32 +52,22 @@ "execution_count": 1, "id": "dca301a4", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "gpt-4\n" - ] - } - ], + "outputs": [], "source": [ + "from datetime import datetime\n", "from tempfile import TemporaryDirectory\n", "\n", "from websockets.sync.client import connect as ws_connect\n", "\n", "import autogen\n", - "from autogen.cache import Cache\n", - "from autogen.io.websockets import IOStream, IOWebsockets\n", + "from autogen.io.websockets import IOWebsockets\n", "\n", "config_list = autogen.config_list_from_json(\n", - " \"OAI_CONFIG_LIST\",\n", + " env_or_file=\"OAI_CONFIG_LIST\",\n", " filter_dict={\n", " \"model\": [\"gpt-4\", \"gpt-3.5-turbo\", \"gpt-3.5-turbo-16k\"],\n", " },\n", - ")\n", - "\n", - "print(config_list[0][\"model\"])" + ")" ] }, { @@ -119,20 +109,25 @@ "\n", " print(\" - on_connect(): Receiving message from client.\", flush=True)\n", "\n", + " # 1. Receive Initial Message\n", " initial_msg = iostream.input()\n", "\n", - " llm_config = {\n", - " \"config_list\": config_list,\n", - " \"stream\": True,\n", - " }\n", - "\n", + " # 2. Instantiate ConversableAgent\n", " agent = autogen.ConversableAgent(\n", " name=\"chatbot\",\n", - " system_message=\"Complete a task given to you and reply TERMINATE when the task is done. If asked about the weather, use tool weather_forecast(city) to get the weather forecast for a city.\",\n", - " llm_config=llm_config,\n", + " system_message=\"Complete a task given to you and reply TERMINATE when the task is done. If asked about the weather, use tool 'weather_forecast(city)' to get the weather forecast for a city.\",\n", + " llm_config={\n", + " \"config_list\": autogen.config_list_from_json(\n", + " env_or_file=\"OAI_CONFIG_LIST\",\n", + " filter_dict={\n", + " \"model\": [\"gpt-4\", \"gpt-3.5-turbo\", \"gpt-3.5-turbo-16k\"],\n", + " },\n", + " ),\n", + " \"stream\": True,\n", + " },\n", " )\n", "\n", - " # create a UserProxyAgent instance named \"user_proxy\"\n", + " # 3. Define UserProxyAgent\n", " user_proxy = autogen.UserProxyAgent(\n", " name=\"user_proxy\",\n", " system_message=\"A proxy for the user.\",\n", @@ -142,23 +137,23 @@ " code_execution_config=False,\n", " )\n", "\n", - " @user_proxy.register_for_execution()\n", - " @agent.register_for_llm(description=\"Weather forecats for a city\")\n", + " # 4. Define Agent-specific Functions\n", " def weather_forecast(city: str) -> str:\n", - " return f\"The weather forecast for {city} is sunny.\"\n", - "\n", - " # we will use a temporary directory as the cache path root to ensure fresh completion each time\n", - " with TemporaryDirectory() as cache_path_root:\n", - " with Cache.disk(cache_path_root=cache_path_root) as cache:\n", - " print(\n", - " f\" - on_connect(): Initiating chat with agent {agent} using message '{initial_msg}'\",\n", - " flush=True,\n", - " )\n", - " user_proxy.initiate_chat( # noqa: F704\n", - " agent,\n", - " message=initial_msg,\n", - " cache=cache,\n", - " )" + " return f\"The weather forecast for {city} at {datetime.now()} is sunny.\"\n", + "\n", + " autogen.register_function(\n", + " weather_forecast, caller=agent, executor=user_proxy, description=\"Weather forecast for a city\"\n", + " )\n", + "\n", + " # 5. Initiate conversation\n", + " print(\n", + " f\" - on_connect(): Initiating chat with agent {agent} using message '{initial_msg}'\",\n", + " flush=True,\n", + " )\n", + " user_proxy.initiate_chat( # noqa: F704\n", + " agent,\n", + " message=initial_msg,\n", + " )" ] }, { @@ -168,15 +163,15 @@ "source": [ "Here's an explanation on how a typical `on_connect` function such as the one in the example above is defined:\n", "\n", - "1. **Receiving Initial Message**: Immediately after establishing a connection, receive an initial message from the client. This step is crucial for understanding the client's request or initiating the conversation flow.\n", - "\n", - "2. **Receiving Initial Message**: Immediately after establishing a connection, receive an initial message from the client. This step is crucial for understanding the client's request or initiating the conversation flow.\n", + "1. **Receive Initial Message**: Immediately after establishing a connection, receive an initial message from the client. This step is crucial for understanding the client's request or initiating the conversation flow.\n", "\n", - "3. **Configure the LLM**: Define the configuration for your large language model (LLM), specifying the list of configurations and the streaming capability. This configuration will be used to tailor the behavior of your conversational agent.\n", + "2. **Instantiate ConversableAgent**: Create an instance of ConversableAgent with a specific system message and the LLM configuration. If you need more than one agent, make sure they don't share the same `llm_config` as \n", + "adding a function to one of them will also attempt to add it to another.\n", "\n", - "4. **Instantiate ConversableAgent and UserProxyAgent**: Create an instance of ConversableAgent with a specific system message and the LLM configuration. Similarly, create a UserProxyAgent instance, defining its termination condition, human input mode, and other relevant parameters.\n", + "2. **Instantiate UserProxyAgent**: Similarly, create a UserProxyAgent instance, defining its termination condition, human input mode, and other relevant parameters. There is no need to define `llm_config` as the UserProxyAgent\n", + "does not use LLM.\n", "\n", - "5. **Define Agent-specific Functions**: If your conversable agent requires executing specific tasks, such as fetching a weather forecast in the example below, define these functions within the on_connect scope. Decorate these functions accordingly to link them with your agents.\n", + "4. **Define Agent-specific Functions**: If your conversable agent requires executing specific tasks, such as fetching a weather forecast in the example above, define these functions within the on_connect scope. Decorate these functions accordingly to link them with your agents.\n", "\n", "5. **Initiate Conversation**: Finally, use the `initiate_chat` method of your `UserProxyAgent` to start the interaction with the conversable agent, passing the initial message and a cache mechanism for efficiency." ] @@ -203,7 +198,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 3, "id": "4fbe004d", "metadata": {}, "outputs": [ @@ -212,11 +207,11 @@ "output_type": "stream", "text": [ " - test_setup() with websocket server running on ws://127.0.0.1:8765.\n", - " - on_connect(): Connected to client using IOWebsockets \n", + " - on_connect(): Connected to client using IOWebsockets \n", " - on_connect(): Receiving message from client.\n", " - Connected to server on ws://127.0.0.1:8765\n", " - Sending message to server.\n", - " - on_connect(): Initiating chat with agent using message 'Check out the weather in Paris and write a poem about it.'\n", + " - on_connect(): Initiating chat with agent using message 'Check out the weather in Paris and write a poem about it.'\n", "\u001b[33muser_proxy\u001b[0m (to chatbot):\n", "\n", "Check out the weather in Paris and write a poem about it.\n", @@ -224,12 +219,10 @@ "--------------------------------------------------------------------------------\n", "\u001b[31m\n", ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", - "\u001b[32m\u001b[32m\u001b[0m\n", - "\n", "\u001b[33mchatbot\u001b[0m (to user_proxy):\n", "\n", "\n", - "\u001b[32m***** Suggested tool Call (call_U5VR0hck9KhDFWPdvmo1Eoke): weather_forecast *****\u001b[0m\n", + "\u001b[32m***** Suggested tool call (call_xFFWe52vwdpgZ8xTRV6adBdy): weather_forecast *****\u001b[0m\n", "Arguments: \n", "{\n", " \"city\": \"Paris\"\n", @@ -243,36 +236,46 @@ "\n", "\u001b[33muser_proxy\u001b[0m (to chatbot):\n", "\n", - "\u001b[32m***** Response from calling tool \"call_U5VR0hck9KhDFWPdvmo1Eoke\" *****\u001b[0m\n", - "The weather forecast for Paris is sunny.\n", + "\u001b[32m***** Response from calling tool (call_xFFWe52vwdpgZ8xTRV6adBdy) *****\u001b[0m\n", + "The weather forecast for Paris at 2024-04-05 12:00:06.206125 is sunny.\n", "\u001b[32m**********************************************************************\u001b[0m\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[31m\n", ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", - "\u001b[32m\u001b[32mIn the city of love, shines the sun above,\n", - "Paris basks in golden rays, a beautiful day to praise.\n", - "Strolling down the Champs ElysΓ©es, the warm light leads the way,\n", - "In the glow, silhouettes dance, a perfect setting for romance.\n", + "\u001b[32m\u001b[32mIn the heart of France, beneath the sun's warm glow,\n", + "Lies the city of Paris, where the Seine waters flow.\n", + "Bathed in sunlight, every street and spire,\n", + "Illuminated each detail, just like a docile fire.\n", + "\n", + "Once monochromatic cityscape, kissed by the sun's bright light,\n", + "Now a kaleidoscope of colors, from morning till the night.\n", + "This sun-swept city sparkles, under the azure dome,\n", + "Her inhabitants find comfort, for they call this city home.\n", "\n", - "In the sunlight, the Seine sparkles bright, reflecting the City of Light,\n", - "Not a cloud in the crystal-clear blue sky, as the doves sail high.\n", - "Sunny Paris so profound, beauty all around,\n", - "Alive under the radiant crown, she wears her sunlight like a gown.\n", + "One can wander in her sunshine, on this perfect weather day,\n", + "And feel the warmth it brings, to chase your blues away.\n", + "For the weather in Paris, is more than just a forecast,\n", + "It is a stage setting for dwellers and tourists amassed.\n", "\n", "TERMINATE\u001b[0m\n", "\n", "\u001b[33mchatbot\u001b[0m (to user_proxy):\n", "\n", - "In the city of love, shines the sun above,\n", - "Paris basks in golden rays, a beautiful day to praise.\n", - "Strolling down the Champs ElysΓ©es, the warm light leads the way,\n", - "In the glow, silhouettes dance, a perfect setting for romance.\n", + "In the heart of France, beneath the sun's warm glow,\n", + "Lies the city of Paris, where the Seine waters flow.\n", + "Bathed in sunlight, every street and spire,\n", + "Illuminated each detail, just like a docile fire.\n", "\n", - "In the sunlight, the Seine sparkles bright, reflecting the City of Light,\n", - "Not a cloud in the crystal-clear blue sky, as the doves sail high.\n", - "Sunny Paris so profound, beauty all around,\n", - "Alive under the radiant crown, she wears her sunlight like a gown.\n", + "Once monochromatic cityscape, kissed by the sun's bright light,\n", + "Now a kaleidoscope of colors, from morning till the night.\n", + "This sun-swept city sparkles, under the azure dome,\n", + "Her inhabitants find comfort, for they call this city home.\n", + "\n", + "One can wander in her sunshine, on this perfect weather day,\n", + "And feel the warmth it brings, to chase your blues away.\n", + "For the weather in Paris, is more than just a forecast,\n", + "It is a stage setting for dwellers and tourists amassed.\n", "\n", "TERMINATE\n", "\n", @@ -330,19 +333,7 @@ "execution_count": 4, "id": "5e55dc06", "metadata": {}, - "outputs": [ - { - "ename": "ModuleNotFoundError", - "evalue": "No module named 'fastapi'", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mModuleNotFoundError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[4], line 4\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mcontextlib\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m asynccontextmanager \u001b[38;5;66;03m# noqa: E402\u001b[39;00m\n\u001b[1;32m 2\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mpathlib\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m Path \u001b[38;5;66;03m# noqa: E402\u001b[39;00m\n\u001b[0;32m----> 4\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mfastapi\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m FastAPI \u001b[38;5;66;03m# noqa: E402\u001b[39;00m\n\u001b[1;32m 5\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mfastapi\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mresponses\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m HTMLResponse \u001b[38;5;66;03m# noqa: E402\u001b[39;00m\n\u001b[1;32m 7\u001b[0m PORT \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m8000\u001b[39m\n", - "\u001b[0;31mModuleNotFoundError\u001b[0m: No module named 'fastapi'" - ] - } - ], + "outputs": [], "source": [ "from contextlib import asynccontextmanager # noqa: E402\n", "from pathlib import Path # noqa: E402\n", @@ -405,10 +396,55 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "id": "d92e50b5", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO: Started server process [5227]\n", + "INFO: Waiting for application startup.\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Websocket server started at ws://127.0.0.1:8080.\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO: Application startup complete.\n", + "INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "INFO: 127.0.0.1:42548 - \"GET / HTTP/1.1\" 200 OK\n", + "INFO: 127.0.0.1:42548 - \"GET /favicon.ico HTTP/1.1\" 404 Not Found\n", + " - on_connect(): Connected to client using IOWebsockets \n", + " - on_connect(): Receiving message from client.\n", + " - on_connect(): Initiating chat with agent using message 'write a poem about lundon'\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO: Shutting down\n", + "INFO: Waiting for application shutdown.\n", + "INFO: Application shutdown complete.\n", + "INFO: Finished server process [5227]\n" + ] + } + ], "source": [ "import uvicorn # noqa: E402\n", "\n", @@ -463,10 +499,53 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "id": "708a98de", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Websocket server started at ws://127.0.0.1:8080.\n", + "HTTP server started at http://localhost:8000\n", + " - on_connect(): Connected to client using IOWebsockets \n", + " - on_connect(): Receiving message from client.\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "127.0.0.1 - - [05/Apr/2024 12:01:51] \"GET / HTTP/1.1\" 200 -\n", + "127.0.0.1 - - [05/Apr/2024 12:01:51] \"GET / HTTP/1.1\" 200 -\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + " - on_connect(): Initiating chat with agent using message 'write a poem about new york'\n", + " - on_connect(): Connected to client using IOWebsockets \n", + " - on_connect(): Receiving message from client.\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "127.0.0.1 - - [05/Apr/2024 12:02:27] \"GET / HTTP/1.1\" 304 -\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + " - on_connect(): Initiating chat with agent using message 'check the weather in london and write a poem about it'\n", + " - HTTP server stopped.\n" + ] + } + ], "source": [ "from http.server import HTTPServer, SimpleHTTPRequestHandler # noqa: E402\n", "\n", @@ -529,7 +608,10 @@ "\n", " with HTTPServer((\"\", PORT), handler) as httpd:\n", " print(\"HTTP server started at http://localhost:\" + str(PORT))\n", - " httpd.serve_forever()" + " try:\n", + " httpd.serve_forever()\n", + " except KeyboardInterrupt:\n", + " print(\" - HTTP server stopped.\", flush=True)" ] }, { @@ -564,7 +646,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.13" + "version": "3.10.14" } }, "nbformat": 4, diff --git a/notebook/agentchats_sequential_chats.ipynb b/notebook/agentchats_sequential_chats.ipynb index 0dd9fa941b4..cffcbfdefcb 100644 --- a/notebook/agentchats_sequential_chats.ipynb +++ b/notebook/agentchats_sequential_chats.ipynb @@ -1128,6 +1128,7 @@ "front_matter": { "description": "Use AutoGen to solve a set of tasks with a sequence of chats.", "tags": [ + "orchestration", "sequential chats" ] }, diff --git a/notebook/agenteval_cq_math.ipynb b/notebook/agenteval_cq_math.ipynb index b1b893975d3..43ea28de1a3 100644 --- a/notebook/agenteval_cq_math.ipynb +++ b/notebook/agenteval_cq_math.ipynb @@ -17,12 +17,12 @@ "source": [ "# Demonstrating the `AgentEval` framework using the task of solving math problems as an example\n", "\n", - "This notebook aims to demonstrate how to `AgentEval` implemented through [AutoGen](https://github.com/microsoft/autogen) works, where we use a math problem-solving task as an example. \n", - "`AgentEval` consists of two key components:\n", + "This notebook aims to demonstrate how to `AgentEval` implemented through [AutoGen](https://github.com/microsoft/autogen) works in an offline scenario, where we use a math problem-solving task as an example. \n", + "`AgentEval` consists of two key steps:\n", "\n", - "- `CriticAgent`: This is an LLM-based agent that generates a list criteria $(c_1, \\dots, c_n)$ to help to evaluate a utility given task.\n", + "- `generate_criteria`: This is an LLM-based function that generates a list of criteria $(c_1, \\dots, c_n)$ to help to evaluate a utility given task.\n", "\n", - "- `QuantifierAgent`: This agent quantifies the performance of any sample task based on the criteria designed by the `CriticAgent` in the following way: $(c_1=a_1, \\dots, c_n=a_n)$\n", + "- `quantify_criteria`: This function quantifies the performance of any sample task based on the criteria generated in the `generate_criteria` step in the following way: $(c_1=a_1, \\dots, c_n=a_n)$\n", "\n", "![AgentEval](../website/blog/2023-11-20-AgentEval/img/agenteval-CQ.png)\n", "\n", @@ -49,7 +49,70 @@ "id": "68lTZZyJ1_BI", "outputId": "15a55fab-e13a-4654-b8cb-ae117478d6d8" }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Defaulting to user installation because normal site-packages is not writeable\n", + "Requirement already satisfied: pyautogen>=0.2.3 in /home/vscode/.local/lib/python3.10/site-packages (0.2.17)\n", + "Requirement already satisfied: docker in /home/vscode/.local/lib/python3.10/site-packages (7.0.0)\n", + "Requirement already satisfied: diskcache in /home/vscode/.local/lib/python3.10/site-packages (from pyautogen>=0.2.3) (5.6.3)\n", + "Requirement already satisfied: flaml in /home/vscode/.local/lib/python3.10/site-packages (from pyautogen>=0.2.3) (2.1.2)\n", + "Requirement already satisfied: tiktoken in /home/vscode/.local/lib/python3.10/site-packages (from pyautogen>=0.2.3) (0.6.0)\n", + "Requirement already satisfied: openai>=1.3 in /home/vscode/.local/lib/python3.10/site-packages (from pyautogen>=0.2.3) (1.14.1)\n", + "Requirement already satisfied: pydantic!=2.6.0,<3,>=1.10 in /home/vscode/.local/lib/python3.10/site-packages (from pyautogen>=0.2.3) (2.6.4)\n", + "Requirement already satisfied: termcolor in /home/vscode/.local/lib/python3.10/site-packages (from pyautogen>=0.2.3) (2.4.0)\n", + "Requirement already satisfied: python-dotenv in /home/vscode/.local/lib/python3.10/site-packages (from pyautogen>=0.2.3) (1.0.1)\n", + "Requirement already satisfied: requests>=2.26.0 in /usr/local/lib/python3.10/site-packages (from docker) (2.31.0)\n", + "Requirement already satisfied: packaging>=14.0 in /usr/local/lib/python3.10/site-packages (from docker) (24.0)\n", + "Requirement already satisfied: urllib3>=1.26.0 in /usr/local/lib/python3.10/site-packages (from docker) (2.2.1)\n", + "Requirement already satisfied: tqdm>4 in /home/vscode/.local/lib/python3.10/site-packages (from openai>=1.3->pyautogen>=0.2.3) (4.66.2)\n", + "Requirement already satisfied: httpx<1,>=0.23.0 in /home/vscode/.local/lib/python3.10/site-packages (from openai>=1.3->pyautogen>=0.2.3) (0.27.0)\n", + "Requirement already satisfied: distro<2,>=1.7.0 in /home/vscode/.local/lib/python3.10/site-packages (from openai>=1.3->pyautogen>=0.2.3) (1.9.0)\n", + "Requirement already satisfied: sniffio in /home/vscode/.local/lib/python3.10/site-packages (from openai>=1.3->pyautogen>=0.2.3) (1.3.1)\n", + "Requirement already satisfied: anyio<5,>=3.5.0 in /home/vscode/.local/lib/python3.10/site-packages (from openai>=1.3->pyautogen>=0.2.3) (4.3.0)\n", + "Requirement already satisfied: typing-extensions<5,>=4.7 in /home/vscode/.local/lib/python3.10/site-packages (from openai>=1.3->pyautogen>=0.2.3) (4.10.0)\n", + "Requirement already satisfied: annotated-types>=0.4.0 in /home/vscode/.local/lib/python3.10/site-packages (from pydantic!=2.6.0,<3,>=1.10->pyautogen>=0.2.3) (0.6.0)\n", + "Requirement already satisfied: pydantic-core==2.16.3 in /home/vscode/.local/lib/python3.10/site-packages (from pydantic!=2.6.0,<3,>=1.10->pyautogen>=0.2.3) (2.16.3)\n", + "Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.10/site-packages (from requests>=2.26.0->docker) (2024.2.2)\n", + "Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.10/site-packages (from requests>=2.26.0->docker) (3.6)\n", + "Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/lib/python3.10/site-packages (from requests>=2.26.0->docker) (3.3.2)\n", + "Requirement already satisfied: NumPy>=1.17 in /home/vscode/.local/lib/python3.10/site-packages (from flaml->pyautogen>=0.2.3) (1.26.4)\n", + "Requirement already satisfied: regex>=2022.1.18 in /home/vscode/.local/lib/python3.10/site-packages (from tiktoken->pyautogen>=0.2.3) (2023.12.25)\n", + "Requirement already satisfied: exceptiongroup>=1.0.2 in /home/vscode/.local/lib/python3.10/site-packages (from anyio<5,>=3.5.0->openai>=1.3->pyautogen>=0.2.3) (1.2.0)\n", + "Requirement already satisfied: httpcore==1.* in /home/vscode/.local/lib/python3.10/site-packages (from httpx<1,>=0.23.0->openai>=1.3->pyautogen>=0.2.3) (1.0.4)\n", + "Requirement already satisfied: h11<0.15,>=0.13 in /home/vscode/.local/lib/python3.10/site-packages (from httpcore==1.*->httpx<1,>=0.23.0->openai>=1.3->pyautogen>=0.2.3) (0.14.0)\n", + "\n", + "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m23.0.1\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m24.0\u001b[0m\n", + "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n", + "Note: you may need to restart the kernel to use updated packages.\n", + "Defaulting to user installation because normal site-packages is not writeable\n", + "Requirement already satisfied: scipy in /home/vscode/.local/lib/python3.10/site-packages (1.12.0)\n", + "Requirement already satisfied: numpy<1.29.0,>=1.22.4 in /home/vscode/.local/lib/python3.10/site-packages (from scipy) (1.26.4)\n", + "\n", + "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m23.0.1\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m24.0\u001b[0m\n", + "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n", + "Note: you may need to restart the kernel to use updated packages.\n", + "Defaulting to user installation because normal site-packages is not writeable\n", + "Requirement already satisfied: matplotlib in /home/vscode/.local/lib/python3.10/site-packages (3.8.3)\n", + "Requirement already satisfied: packaging>=20.0 in /usr/local/lib/python3.10/site-packages (from matplotlib) (24.0)\n", + "Requirement already satisfied: pyparsing>=2.3.1 in /home/vscode/.local/lib/python3.10/site-packages (from matplotlib) (3.1.2)\n", + "Requirement already satisfied: contourpy>=1.0.1 in /home/vscode/.local/lib/python3.10/site-packages (from matplotlib) (1.2.0)\n", + "Requirement already satisfied: fonttools>=4.22.0 in /home/vscode/.local/lib/python3.10/site-packages (from matplotlib) (4.50.0)\n", + "Requirement already satisfied: python-dateutil>=2.7 in /home/vscode/.local/lib/python3.10/site-packages (from matplotlib) (2.9.0.post0)\n", + "Requirement already satisfied: cycler>=0.10 in /home/vscode/.local/lib/python3.10/site-packages (from matplotlib) (0.12.1)\n", + "Requirement already satisfied: pillow>=8 in /home/vscode/.local/lib/python3.10/site-packages (from matplotlib) (10.2.0)\n", + "Requirement already satisfied: numpy<2,>=1.21 in /home/vscode/.local/lib/python3.10/site-packages (from matplotlib) (1.26.4)\n", + "Requirement already satisfied: kiwisolver>=1.3.1 in /home/vscode/.local/lib/python3.10/site-packages (from matplotlib) (1.4.5)\n", + "Requirement already satisfied: six>=1.5 in /home/vscode/.local/lib/python3.10/site-packages (from python-dateutil>=2.7->matplotlib) (1.16.0)\n", + "\n", + "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m23.0.1\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m24.0\u001b[0m\n", + "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n", + "Note: you may need to restart the kernel to use updated packages.\n" + ] + } + ], "source": [ "%pip install \"pyautogen>=0.2.3\" docker\n", "%pip install scipy\n", @@ -63,11 +126,6 @@ }, "source": [ "## Set your API Endpoint\n", - "\n", - "* The [`config_list_openai_aoai`](https://microsoft.github.io/autogen/docs/reference/oai/openai_utils#config_list_openai_aoai) function tries to create a list of configurations using Azure OpenAI endpoints and OpenAI endpoints. It assumes the api keys and api bases are stored in the corresponding environment variables or local txt files:\n", - " - OpenAI API key: os.environ[\"OPENAI_API_KEY\"] or `openai_api_key_file=\"key_openai.txt\"`.\n", - " - Azure OpenAI API key: os.environ[\"AZURE_OPENAI_API_KEY\"] or `aoai_api_key_file=\"key_aoai.txt\"`. Multiple keys can be stored, one per line.\n", - " - Azure OpenAI API base: os.environ[\"AZURE_OPENAI_API_BASE\"] or `aoai_api_base_file=\"base_aoai.txt\"`. Multiple bases can be stored, one per line.\n", "* The [`config_list_from_json`](https://microsoft.github.io/autogen/docs/reference/oai/openai_utils#config_list_from_json) function loads a list of configurations from an environment variable or a json file. It first looks for an environment variable with a specified name. The value of the environment variable needs to be a valid json string. If that variable is not found, it looks for a json file with the same name. It filters the configs by filter_dict.\n", "\n", "You can set the value of config_list in any way you prefer. Please refer to this [notebook](https://github.com/microsoft/autogen/blob/main/notebook/oai_openai_utils.ipynb) for full code examples of the different methods.\n" @@ -82,7 +140,6 @@ "outputs": [], "source": [ "import json\n", - "\n", "import os\n", "from pathlib import Path\n", "\n", @@ -91,68 +148,11 @@ "import scipy.stats as stats\n", "\n", "import autogen\n", + "from autogen.agentchat.contrib.agent_eval.agent_eval import generate_criteria, quantify_criteria\n", + "from autogen.agentchat.contrib.agent_eval.criterion import Criterion\n", + "from autogen.agentchat.contrib.agent_eval.task import Task\n", "\n", - "config_list = autogen.config_list_from_json(\n", - " \"OAI_CONFIG_LIST\",\n", - " filter_dict={\n", - " \"model\": [\"gpt-4\"],\n", - " },\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "fBZ-XFXy1_BJ" - }, - "source": [ - "\n", - "## Construct `CriticAgent`\n", - "\n", - "We construct the planning agent named `critic` and a user proxy agent for the critic named `critic_user`. We specify `human_input_mode` as \"NEVER\" in the user proxy agent, ensuring that it will never ask for human feedback. Additionally, we define the `ask_critic` function to send a message to the critic and retrieve the criteria from the critic.\n" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "id": "9XAeyjd11_BK" - }, - "outputs": [], - "source": [ - "critic = autogen.AssistantAgent(\n", - " name=\"critic\",\n", - " llm_config={\"config_list\": config_list},\n", - " system_message=\"\"\"You are a helpful assistant. You suggest criteria for evaluating different tasks. They should be dinstinguishable, quantifieable and not redundant.\n", - " Convert the evaluation criteria into a dictionary where the keys are the criteria.\n", - " The value of each key is a dictionary as follows {\"description\": criteria description , \"accepted_values\": possible accepted inputs for this key}\n", - " Make sure the keys are criteria for assessing the given task. \"accepted_values\" include the acceptable inputs for each key that are fine-grained and preferably multi-graded levels. \"description\" includes the criterion description.\n", - " Return the dictionary.\"\"\",\n", - ")\n", - "\n", - "critic_user = autogen.UserProxyAgent(\n", - " name=\"critic_user\",\n", - " max_consecutive_auto_reply=0, # terminate without auto-reply\n", - " human_input_mode=\"NEVER\",\n", - " code_execution_config={\n", - " \"use_docker\": False\n", - " }, # Please set use_docker=True if docker is available to run the generated code. Using docker is safer than running the generated code directly.\n", - ")\n", - "\n", - "\n", - "def ask_critic(message):\n", - " \"\"\"\n", - " Initiate a chat with the critic user and return the last message received from the planner.\n", - "\n", - " Args:\n", - " - message (str): The message to be sent to the critic user.\n", - "\n", - " Returns:\n", - " - str: The content of the last message received.\n", - " \"\"\"\n", - " critic_user.initiate_chat(critic, message=message)\n", - " # return the last received from the planner\n", - " return critic_user.messagelast_message()[\"content\"]" + "config_list = autogen.config_list_from_json(\"OAI_CONFIG_LIST\")" ] }, { @@ -168,207 +168,138 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "metadata": { "id": "5H1WRs_wkiK0" }, - "outputs": [], - "source": [ - "def read_without_groundtruth(file_name):\n", - " \"\"\"\n", - " Read the mathproblem logs - bypassing any information about the ground truths.\n", - "\n", - " Args:\n", - " - file_name (str): The single log file that wants to get evaluated.\n", - "\n", - " Returns:\n", - " - str: The log file without any information about the ground truth answer of the problem.\n", - " \"\"\"\n", - " f = open(file_name, \"r\").readlines()\n", - " output_dictionary = \"\"\n", - " for line in f:\n", - " if \"is_correct\" not in line and \"correct_ans\" not in line and \"check_result\" not in line:\n", - " output_dictionary += line\n", - " elif \"is_correct\" in line:\n", - " correctness = line.replace(\",\", \"\").split(\":\")[-1].rstrip().strip()\n", - " return [output_dictionary, correctness]\n", - "\n", - "\n", - "# Reading one successful and one failed example of the task\n", - "response_successful = read_without_groundtruth(\n", - " \"../test/test_files/agenteval-in-out/samples/sample_math_response_successful.txt\"\n", - ")[0]\n", - "response_failed = read_without_groundtruth(\n", - " \"../test/test_files/agenteval-in-out/samples/sample_math_response_failed.txt\"\n", - ")[0]\n", - "\n", - "task = {\n", - " \"name\": \"Math problem solving\",\n", - " \"description\": \"Given any question, the system needs to solve the problem as consisely and accurately as possible\",\n", - " \"successful_response\": response_successful,\n", - " \"failed_response\": response_failed,\n", - "}\n", - "\n", - "sys_msg = f\"\"\"Task: {task[\"name\"]}.\n", - "Task description: {task[\"description\"]}\n", - "Task successful example: {task[\"successful_response\"]}\n", - "Task failed example: {task[\"failed_response\"]}\n", - "\"\"\"" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Vu70o024lenI" - }, - "source": [ - "# The Criteria\n", - "Now, we print the designed criteria for assessing math problems. " - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "k9DsDB5hqvtG", - "outputId": "0edd7a0c-b031-4f67-efc6-1a1e77066921" - }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "\u001b[33mcritic_user\u001b[0m (to critic):\n", + "\u001b[33mcritic_user\u001b[0m (to chat_manager):\n", "\n", "Task: Math problem solving.\n", - "Task description: Given any question, the system needs to solve the problem as consisely and accurately as possible\n", - "Task successful example: {\n", - " \"problem\": \"What is the sum of all the distinct positive two-digit factors of 144?\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Number Theory\",\n", - " \"solution\": \"Prime factorize $144=2^4\\\\cdot3^2$. The sum of the positive two-digit factors of 144 is $2^4+2\\\\cdot3^2+2^2\\\\cdot3+2^2\\\\cdot3^2+2^3\\\\cdot3+2^3\\\\cdot3^2+2^4\\\\cdot3=\\\\boxed{226}.$\",\n", - " \"problem_id\": \"0\",\n", - " \"response_with_ans\": \"To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere's a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\\\"The sum of all the distinct positive two-digit factors of 144 is:\\\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.\",\n", - " \"round\": 0,\n", - " \"messages\": [\n", + " Task description: Given any question, the system needs to solve the problem as consisely and accurately as possible\n", + " Task successful example: {'problem': 'What is the sum of all the distinct positive two-digit factors of 144?', 'level': 'Level 5', 'type': 'Number Theory', 'solution': 'Prime factorize $144=2^4\\\\cdot3^2$. The sum of the positive two-digit factors of 144 is $2^4+2\\\\cdot3^2+2^2\\\\cdot3+2^2\\\\cdot3^2+2^3\\\\cdot3+2^3\\\\cdot3^2+2^4\\\\cdot3=\\\\boxed{226}.$', 'problem_id': '0', 'response_with_ans': 'To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere\\'s a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\"The sum of all the distinct positive two-digit factors of 144 is:\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.', 'round': 0, 'messages': [{'content': 'What is the sum of all the distinct positive two-digit factors of 144?', 'role': 'user'}, {'content': 'To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere\\'s a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\"The sum of all the distinct positive two-digit factors of 144 is:\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.', 'role': 'assistant'}], 'time': 11.140539407730103, 'trial': -1}\n", + " Task failed example: {'problem': 'Find all $x$ that satisfy the inequality $(2x+10)(x+3)<(3x+9)(x+8)$. Express your answer in interval notation.', 'level': 'Level 5', 'type': 'Algebra', 'solution': 'We have \\\\begin{align*} (2x+10)(x+3)&<(3x+9)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)&<3(x+3)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)-3(x+3)(x+8)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (2x+10-(3x+24))(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (-x-14)(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (x+14)(x+3)&>0.\\n\\\\end{align*} This inequality is satisfied if and only if $(x+14)$ and $(x+3)$ are either both positive or both negative. Both factors are positive for $x>-3$ and both factors are negative for $x<-14$. When $-14 0$ and then find intervals where the inequality holds true.\\n\\nLet's first find the factorized form of the quadratic:\", 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': \"I apologize for the confusion in my previous message. Let's find the factorized form of the quadratic using Python code.\\n\\n```python\\nfrom sympy import symbols, factor\\r\\nx = symbols('x')\\r\\nfactorized = factor(x**2 + 17*x + 42)\\r\\nfactorized\\r\\n```\\n\\nPlease run the Python code above to find the factorized form of the quadratic, and we can proceed to determine the intervals where the inequality holds true.\", 'role': 'assistant'}], 'time': 24.91333508491516, 'trial': -1}\n", + " \n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[31m\n", + ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", + "\u001b[33mcritic\u001b[0m (to chat_manager):\n", + "\n", + "[\n", " {\n", - " \"content\": \"What is the sum of all the distinct positive two-digit factors of 144?\",\n", - " \"role\": \"user\"\n", + " \"name\": \"Accuracy\",\n", + " \"description\": \"The solution must be correct and adhere strictly to mathematical principles and techniques appropriate for the problem.\",\n", + " \"accepted_values\": [\"Correct\", \"Minor errors\", \"Major errors\", \"Incorrect\"]\n", " },\n", " {\n", - " \"content\": \"To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere's a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\\\"The sum of all the distinct positive two-digit factors of 144 is:\\\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 11.140539407730103,\n", - " \"trial\": -1\n", - "}\n", - "\n", - "Task failed example: {\n", - " \"problem\": \"Find all $x$ that satisfy the inequality $(2x+10)(x+3)<(3x+9)(x+8)$. Express your answer in interval notation.\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Algebra\",\n", - " \"solution\": \"We have \\\\begin{align*} (2x+10)(x+3)&<(3x+9)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)&<3(x+3)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)-3(x+3)(x+8)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (2x+10-(3x+24))(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (-x-14)(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (x+14)(x+3)&>0.\\n\\\\end{align*} This inequality is satisfied if and only if $(x+14)$ and $(x+3)$ are either both positive or both negative. Both factors are positive for $x>-3$ and both factors are negative for $x<-14$. When $-14 0$ and then find intervals where the inequality holds true.\\n\\nLet's first find the factorized form of the quadratic:\",\n", - " \"role\": \"assistant\"\n", + " \"name\": \"Relevance\",\n", + " \"description\": \"The content of the response must be relevant to the question posed and should address the specific problem requirements.\",\n", + " \"accepted_values\": [\"Highly relevant\", \"Relevant\", \"Somewhat relevant\", \"Not relevant\"]\n", " },\n", " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", + " \"name\": \"Efficiency\",\n", + " \"description\": \"The solution should be derived in a time-effective manner, considering the complexity of the problem.\",\n", + " \"accepted_values\": [\"Highly efficient\", \"Efficient\", \"Inefficient\", \"Redundant\"]\n", " },\n", " {\n", - " \"content\": \"I apologize for the confusion in my previous message. Let's find the factorized form of the quadratic using Python code.\\n\\n```python\\nfrom sympy import symbols, factor\\r\\nx = symbols('x')\\r\\nfactorized = factor(x**2 + 17*x + 42)\\r\\nfactorized\\r\\n```\\n\\nPlease run the Python code above to find the factorized form of the quadratic, and we can proceed to determine the intervals where the inequality holds true.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 24.91333508491516,\n", - " \"trial\": -1\n", - "}\n", - "\n", - "\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mcritic\u001b[0m (to critic_user):\n", - "\n", - "In evaluating math problem-solving tasks, we can establish certain criteria to assess the level of success in solving the math problems. Below are the criteria with their corresponding descriptions and the accepted values:\n", - "\n", - "```python\n", - "evaluation_criteria = {\n", - " \"accuracy\": {\n", - " \"description\": \"Correctness of the final answer provided.\",\n", - " \"accepted_values\": {\n", - " \"correct\": \"The given answer is correct.\",\n", - " \"incorrect\": \"The given answer is incorrect.\",\n", - " \"partial\": \"The answer is partially correct with minor errors.\"\n", - " }\n", + " \"name\": \"Logic and Structure\",\n", + " \"description\": \"The reasoning should be logical and the information structured in a clear and understandable sequence.\",\n", + " \"accepted_values\": [\"Exceptionally clear\", \"Clear\", \"Somewhat clear\", \"Confusing\"]\n", " },\n", - " \"completeness\": {\n", - " \"description\": \"The extent to which all necessary steps are included and properly documented.\",\n", - " \"accepted_values\": {\n", - " \"complete\": \"All necessary steps are included and properly documented.\",\n", - " \"incomplete\": \"Some steps are missing or not properly documented.\",\n", - " \"overly_detailed\": \"The solution contains unnecessary detail that doesn't contribute to understanding.\"\n", - " }\n", + " {\n", + " \"name\": \"Use of Resources\",\n", + " \"description\": \"The response should make appropriate and optimal use of external resources or tools (e.g., Python scripts) when necessary.\",\n", + " \"accepted_values\": [\"Optimal\", \"Appropriate\", \"Underutilized\", \"Overreliance\"]\n", " },\n", - " \"efficiency\": {\n", - " \"description\": \"The method used to solve the problem is concise and does not include redundant steps.\",\n", - " \"accepted_values\": {\n", - " \"efficient\": \"The solution is found through the most direct method with no superfluous steps.\",\n", - " \"inefficient\": \"The method used is not the most direct and may include redundant steps.\",\n", - " \"acceptable\": \"The method used is reasonably direct with little redundancy.\"\n", - " }\n", + " {\n", + " \"name\": \"Mathematical Notation\",\n", + " \"description\": \"The use of proper and standard mathematical notation in the solution and explanation.\",\n", + " \"accepted_values\": [\"Excellent\", \"Good\", \"Adequate\", \"Poor\"]\n", " },\n", - " \"methodology\": {\n", - " \"description\": \"The approach used to solve the problem, including the use of formulas, theorems, and problem-solving techniques.\",\n", - " \"accepted_values\": {\n", - " \"appropriate\": \"The methodology used is appropriate for the problem.\",\n", - " \"inappropriate\": \"The methodology used is not suitable for the problem.\",\n", - " \"partially_appropriate\": \"The methodology used is partially suitable but could be improved.\"\n", - " }\n", + " {\n", + " \"name\": \"Explanation and Justification\",\n", + " \"description\": \"There should be a clear explanation, rationale, or justification for each step taken towards the solution.\",\n", + " \"accepted_values\": [\"Thorough\", \"Adequate\", \"Insufficient\", \"Missing\"]\n", " },\n", - " \"clarity\": {\n", - " \"description\": \"The ease with which the solution can be understood by others.\",\n", - " \"accepted_values\": {\n", - " \"clear\": \"The solution is presented in a clear, logical manner that is easy to follow.\",\n", - " \"unclear\": \"The solution is difficult to follow or understand.\",\n", - " \"somewhat_clear\": \"The solution is generally clear but could be improved in some areas for better understanding.\"\n", - " }\n", + " {\n", + " \"name\": \"Correctness of Answer Format\",\n", + " \"description\": \"The answer should be presented in the format requested in the problem (e.g., interval notation, simplified form).\",\n", + " \"accepted_values\": [\"Perfectly formatted\", \"Properly formatted\", \"Slightly incorrect format\", \"Improperly formatted\"]\n", " },\n", - " \"use_of_language\": {\n", - " \"description\": \"The correctness and appropriateness of mathematical language and notation.\",\n", - " \"accepted_values\": {\n", - " \"appropriate\": \"The language and notation are mathematically sound and correctly applied.\",\n", - " \"inappropriate\": \"The language and notation have errors or are misapplied.\",\n", - " \"mostly_appropriate\": \"The language and notation are mostly correct, but there are minor errors or inconsistencies.\"\n", - " }\n", + " {\n", + " \"name\": \"Handling of Edge Cases\",\n", + " \"description\": \"The solution should correctly handle any special or edge cases that may arise in the problem.\",\n", + " \"accepted_values\": [\"Complete\", \"Most cases\", \"Some cases\", \"No consideration\"]\n", " }\n", - "}\n", - "```\n", - "\n", - "These criteria should provide a comprehensive framework for evaluating math problem-solving tasks in terms of accuracy, completeness, efficiency, and clarity.\n", + "]\n", "\n", "--------------------------------------------------------------------------------\n" ] } ], "source": [ - "current_task_name = \"_\".join(task[\"name\"].split()).lower()\n", - "gen_criteria = critic_user.initiate_chat(critic, message=sys_msg)\n", - "criteria = critic_user.last_message()\n", + "def remove_ground_truth(test_case):\n", + " test_details = json.loads(test_case)\n", + " # need to remove the ground truth from the test details\n", + " correctness = test_details.pop(\"is_correct\", None)\n", + " test_details.pop(\"correct_ans\", None)\n", + " test_details.pop(\"check_result\", None)\n", + " return str(test_details), correctness\n", + "\n", + "\n", + "# Reading one successful and one failed example of the task\n", + "success_str = open(\"../test/test_files/agenteval-in-out/samples/sample_math_response_successful.txt\", \"r\").read()\n", + "response_successful = remove_ground_truth(success_str)[0]\n", + "failed_str = open(\"../test/test_files/agenteval-in-out/samples/sample_math_response_failed.txt\", \"r\").read()\n", + "response_failed = remove_ground_truth(failed_str)[0]\n", + "\n", + "task = Task(\n", + " **{\n", + " \"name\": \"Math problem solving\",\n", + " \"description\": \"Given any question, the system needs to solve the problem as consisely and accurately as possible\",\n", + " \"successful_response\": response_successful,\n", + " \"failed_response\": response_failed,\n", + " }\n", + ")\n", + "\n", + "criteria = generate_criteria(task=task, llm_config={\"config_list\": config_list}, max_round=8)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Vu70o024lenI" + }, + "source": [ + "# The Criteria\n", + "Now, we print the designed criteria for assessing math problems. " + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "k9DsDB5hqvtG", + "outputId": "0edd7a0c-b031-4f67-efc6-1a1e77066921" + }, + "outputs": [], + "source": [ + "current_task_name = \"_\".join(task.name.split()).lower()\n", "cr_file = open(f\"../test/test_files/agenteval-in-out/{current_task_name}_criteria.json\", \"w\")\n", - "cr_file.write(criteria[\"content\"])\n", + "cr_file.write(Criterion.write_json(criteria))\n", "cr_file.close()" ] }, @@ -378,7 +309,7 @@ "id": "PETPZluOEGCR" }, "source": [ - "*Note :* You can also define and use your own criteria by editing `criteria.txt`" + "*Note :* You can also define and use your own criteria in order to feed into the quantifier." ] }, { @@ -389,40 +320,21 @@ "source": [ "# The `QuantifierAgent`\n", "\n", - "Once we have the criteria, we need to quantify a new sample based on the designed criteria and its accepted values. This will be done through `QuantifierAgent` agent as follows. \n", - "We note that can skip the designed criteria by the agent and use your own defined criteria in `criteria_file`." + "Once we have the criteria, we need to quantify a new sample based on the designed criteria and its accepted values. This will be done through `quantify_criteria` from agent_eval. \n", + "Again, you can use your own defined criteria in `criteria_file`." ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "metadata": { "id": "4uUkZJh_subA" }, "outputs": [], "source": [ "criteria_file = f\"../test/test_files/agenteval-in-out/{current_task_name}_criteria.json\"\n", - "quantifier = autogen.AssistantAgent(\n", - " name=\"quantifier\",\n", - " llm_config={\"config_list\": config_list},\n", - " system_message=\"\"\"You are a helpful assistant. You quantify the output of different tasks based on the given criteria.\n", - " The criterion is given in a dictionary format where each key is a dintinct criteria.\n", - " The value of each key is a dictionary as follows {\"description\": criteria description , \"accepted_values\": possible accepted inputs for this key}\n", - " You are going to quantify each of the crieria for a given task based on the task description.\n", - " Return a dictionary where the keys are the criteria and the values are the assessed performance based on accepted values for each criteria.\n", - " Return only the dictionary.\"\"\",\n", - ")\n", - "\n", - "quantifier_user = autogen.UserProxyAgent(\n", - " name=\"quantifier_user\",\n", - " max_consecutive_auto_reply=0, # terminate without auto-reply\n", - " human_input_mode=\"NEVER\",\n", - " code_execution_config={\n", - " \"use_docker\": False\n", - " }, # Please set use_docker=True if docker is available to run the generated code. Using docker is safer than running the generated code directly.\n", - ")\n", - "\n", - "dictionary_for_eval = open(criteria_file, \"r\").read()" + "criteria = open(criteria_file, \"r\").read()\n", + "criteria = Criterion.parse_json_str(criteria)" ] }, { @@ -434,41 +346,6 @@ "## Running the quantifier on a single test case" ] }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": { - "id": "zQ0H3sy8l-Ai" - }, - "outputs": [], - "source": [ - "def get_quantifier(file, criteria_file):\n", - " \"\"\"\n", - " Running quantifier agent on individual log.\n", - "\n", - " Args:\n", - " - file (str): The log path.\n", - " - file (str): The criteria jason file path\n", - " Returns:\n", - " - dict: A dictionary including the actual success of the problem as well as estimated performance by the agent eval.\n", - " {\"actual_success\":actual_label, \"estimated_performance\" : a dictionary of all the criteria and their quantified estimated performance.} }\n", - " \"\"\"\n", - " dictionary_for_eval = open(criteria_file, \"r\").read()\n", - "\n", - " test_case, actual_label = read_without_groundtruth(file)\n", - " print(\"actual label for this case: \", actual_label)\n", - " cq_results = quantifier_user.initiate_chat( # noqa: F841\n", - " quantifier,\n", - " message=sys_msg\n", - " + \"Evaluation dictionary: \"\n", - " + str(dictionary_for_eval)\n", - " + \"actual test case to evaluate: \"\n", - " + test_case,\n", - " )\n", - " quantified_results = quantifier_user.last_message()\n", - " return {\"actual_success\": actual_label, \"estimated_performance\": quantified_results[\"content\"]}" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -478,7 +355,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 6, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -491,176 +368,173 @@ "name": "stdout", "output_type": "stream", "text": [ - "actual label for this case: true\n", "\u001b[33mquantifier_user\u001b[0m (to quantifier):\n", "\n", "Task: Math problem solving.\n", - "Task description: Given any question, the system needs to solve the problem as consisely and accurately as possible\n", - "Task successful example: {\n", - " \"problem\": \"What is the sum of all the distinct positive two-digit factors of 144?\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Number Theory\",\n", - " \"solution\": \"Prime factorize $144=2^4\\\\cdot3^2$. The sum of the positive two-digit factors of 144 is $2^4+2\\\\cdot3^2+2^2\\\\cdot3+2^2\\\\cdot3^2+2^3\\\\cdot3+2^3\\\\cdot3^2+2^4\\\\cdot3=\\\\boxed{226}.$\",\n", - " \"problem_id\": \"0\",\n", - " \"response_with_ans\": \"To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere's a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\\\"The sum of all the distinct positive two-digit factors of 144 is:\\\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.\",\n", - " \"round\": 0,\n", - " \"messages\": [\n", - " {\n", - " \"content\": \"What is the sum of all the distinct positive two-digit factors of 144?\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere's a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\\\"The sum of all the distinct positive two-digit factors of 144 is:\\\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 11.140539407730103,\n", - " \"trial\": -1\n", - "}\n", - "\n", - "Task failed example: {\n", - " \"problem\": \"Find all $x$ that satisfy the inequality $(2x+10)(x+3)<(3x+9)(x+8)$. Express your answer in interval notation.\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Algebra\",\n", - " \"solution\": \"We have \\\\begin{align*} (2x+10)(x+3)&<(3x+9)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)&<3(x+3)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)-3(x+3)(x+8)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (2x+10-(3x+24))(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (-x-14)(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (x+14)(x+3)&>0.\\n\\\\end{align*} This inequality is satisfied if and only if $(x+14)$ and $(x+3)$ are either both positive or both negative. Both factors are positive for $x>-3$ and both factors are negative for $x<-14$. When $-14 0$ and then find intervals where the inequality holds true.\\n\\nLet's first find the factorized form of the quadratic:\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"I apologize for the confusion in my previous message. Let's find the factorized form of the quadratic using Python code.\\n\\n```python\\nfrom sympy import symbols, factor\\r\\nx = symbols('x')\\r\\nfactorized = factor(x**2 + 17*x + 42)\\r\\nfactorized\\r\\n```\\n\\nPlease run the Python code above to find the factorized form of the quadratic, and we can proceed to determine the intervals where the inequality holds true.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 24.91333508491516,\n", - " \"trial\": -1\n", - "}\n", - "\n", - "Evaluation dictionary: In evaluating math problem-solving tasks, we can establish certain criteria to assess the level of success in solving the math problems. Below are the criteria with their corresponding descriptions and the accepted values:\n", - "\n", - "```python\n", - "evaluation_criteria = {\n", - " \"accuracy\": {\n", - " \"description\": \"Correctness of the final answer provided.\",\n", - " \"accepted_values\": {\n", - " \"correct\": \"The given answer is correct.\",\n", - " \"incorrect\": \"The given answer is incorrect.\",\n", - " \"partial\": \"The answer is partially correct with minor errors.\"\n", - " }\n", - " },\n", - " \"completeness\": {\n", - " \"description\": \"The extent to which all necessary steps are included and properly documented.\",\n", - " \"accepted_values\": {\n", - " \"complete\": \"All necessary steps are included and properly documented.\",\n", - " \"incomplete\": \"Some steps are missing or not properly documented.\",\n", - " \"overly_detailed\": \"The solution contains unnecessary detail that doesn't contribute to understanding.\"\n", - " }\n", - " },\n", - " \"efficiency\": {\n", - " \"description\": \"The method used to solve the problem is concise and does not include redundant steps.\",\n", - " \"accepted_values\": {\n", - " \"efficient\": \"The solution is found through the most direct method with no superfluous steps.\",\n", - " \"inefficient\": \"The method used is not the most direct and may include redundant steps.\",\n", - " \"acceptable\": \"The method used is reasonably direct with little redundancy.\"\n", - " }\n", - " },\n", - " \"methodology\": {\n", - " \"description\": \"The approach used to solve the problem, including the use of formulas, theorems, and problem-solving techniques.\",\n", - " \"accepted_values\": {\n", - " \"appropriate\": \"The methodology used is appropriate for the problem.\",\n", - " \"inappropriate\": \"The methodology used is not suitable for the problem.\",\n", - " \"partially_appropriate\": \"The methodology used is partially suitable but could be improved.\"\n", - " }\n", - " },\n", - " \"clarity\": {\n", - " \"description\": \"The ease with which the solution can be understood by others.\",\n", - " \"accepted_values\": {\n", - " \"clear\": \"The solution is presented in a clear, logical manner that is easy to follow.\",\n", - " \"unclear\": \"The solution is difficult to follow or understand.\",\n", - " \"somewhat_clear\": \"The solution is generally clear but could be improved in some areas for better understanding.\"\n", - " }\n", - " },\n", - " \"use_of_language\": {\n", - " \"description\": \"The correctness and appropriateness of mathematical language and notation.\",\n", - " \"accepted_values\": {\n", - " \"appropriate\": \"The language and notation are mathematically sound and correctly applied.\",\n", - " \"inappropriate\": \"The language and notation have errors or are misapplied.\",\n", - " \"mostly_appropriate\": \"The language and notation are mostly correct, but there are minor errors or inconsistencies.\"\n", - " }\n", - " }\n", - "}\n", - "```\n", - "\n", - "These criteria should provide a comprehensive framework for evaluating math problem-solving tasks in terms of accuracy, completeness, efficiency, and clarity.actual test case to evaluate: {\n", - " \"problem\": \"Find $24^{-1} \\\\pmod{11^2}$. That is, find the residue $b$ for which $24b \\\\equiv 1\\\\pmod{11^2}$.\\n\\nExpress your answer as an integer from $0$ to $11^2-1$, inclusive.\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Number Theory\",\n", - " \"solution\": \"Since $5 \\\\times 24 = 120 = 121 - 1$, it follows that $-5 \\\\times 24 \\\\equiv 1 \\\\pmod{121}$. Adding 121 to $-5$ to make it positive, we find $(-5 + 121) \\\\times 24 \\\\equiv 116 \\\\times 24 \\\\equiv 1 \\\\pmod{121}$, so it follows that the modular inverse of $24$ is $\\\\boxed{116}$ when taken modulo $121$.\",\n", - " \"problem_id\": \"5\",\n", - " \"response_with_ans\": \"To find the modular inverse of 24 modulo 11^2, we can use the Extended Euclidean Algorithm. Here is a Python function to compute the modular inverse using this algorithm:\\n\\n```python\\ndef mod_inverse(a, m):\\n g, x, _ = extended_gcd(a, m)\\n if g != 1:\\n raise Exception(f\\\"{a} and {m} are not coprime.\\\")\\n return x % m\\n\\ndef extended_gcd(a, b):\\n if a == 0:\\n return b, 0, 1\\n else:\\n g, x, y = extended_gcd(b % a, a)\\n return g, y - (b // a) * x, x\\n```\\n\\nLet's use the above function to find the modular inverse of 24 modulo 11^2:\\n\\n```python\\na = 24\\nm = 11**2\\nmod_inverse(a, m)\\n```\\n\\nI will execute the above code to find the modular inverse of 24 modulo 11^2.\",\n", - " \"round\": 0,\n", - " \"messages\": [\n", - " {\n", - " \"content\": \"Find $24^{-1} \\\\pmod{11^2}$. That is, find the residue $b$ for which $24b \\\\equiv 1\\\\pmod{11^2}$.\\n\\nExpress your answer as an integer from $0$ to $11^2-1$, inclusive.\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"To find the modular inverse of 24 modulo 11^2, we can use the Extended Euclidean Algorithm. Here is a Python function to compute the modular inverse using this algorithm:\\n\\n```python\\ndef mod_inverse(a, m):\\n g, x, _ = extended_gcd(a, m)\\n if g != 1:\\n raise Exception(f\\\"{a} and {m} are not coprime.\\\")\\n return x % m\\n\\ndef extended_gcd(a, b):\\n if a == 0:\\n return b, 0, 1\\n else:\\n g, x, y = extended_gcd(b % a, a)\\n return g, y - (b // a) * x, x\\n```\\n\\nLet's use the above function to find the modular inverse of 24 modulo 11^2:\\n\\n```python\\na = 24\\nm = 11**2\\nmod_inverse(a, m)\\n```\\n\\nI will execute the above code to find the modular inverse of 24 modulo 11^2.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 13.481226921081543,\n", - " \"trial\": -1\n", - "}\n", - "\n", + " Task description: Given any question, the system needs to solve the problem as consisely and accurately as possible\n", + " Task successful example: {'problem': 'What is the sum of all the distinct positive two-digit factors of 144?', 'level': 'Level 5', 'type': 'Number Theory', 'solution': 'Prime factorize $144=2^4\\\\cdot3^2$. The sum of the positive two-digit factors of 144 is $2^4+2\\\\cdot3^2+2^2\\\\cdot3+2^2\\\\cdot3^2+2^3\\\\cdot3+2^3\\\\cdot3^2+2^4\\\\cdot3=\\\\boxed{226}.$', 'problem_id': '0', 'response_with_ans': 'To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere\\'s a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\"The sum of all the distinct positive two-digit factors of 144 is:\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.', 'round': 0, 'messages': [{'content': 'What is the sum of all the distinct positive two-digit factors of 144?', 'role': 'user'}, {'content': 'To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere\\'s a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\"The sum of all the distinct positive two-digit factors of 144 is:\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.', 'role': 'assistant'}], 'time': 11.140539407730103, 'trial': -1}\n", + " Task failed example: {'problem': 'Find all $x$ that satisfy the inequality $(2x+10)(x+3)<(3x+9)(x+8)$. Express your answer in interval notation.', 'level': 'Level 5', 'type': 'Algebra', 'solution': 'We have \\\\begin{align*} (2x+10)(x+3)&<(3x+9)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)&<3(x+3)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)-3(x+3)(x+8)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (2x+10-(3x+24))(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (-x-14)(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (x+14)(x+3)&>0.\\n\\\\end{align*} This inequality is satisfied if and only if $(x+14)$ and $(x+3)$ are either both positive or both negative. Both factors are positive for $x>-3$ and both factors are negative for $x<-14$. When $-14 0$ and then find intervals where the inequality holds true.\\n\\nLet's first find the factorized form of the quadratic:\", 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': \"I apologize for the confusion in my previous message. Let's find the factorized form of the quadratic using Python code.\\n\\n```python\\nfrom sympy import symbols, factor\\r\\nx = symbols('x')\\r\\nfactorized = factor(x**2 + 17*x + 42)\\r\\nfactorized\\r\\n```\\n\\nPlease run the Python code above to find the factorized form of the quadratic, and we can proceed to determine the intervals where the inequality holds true.\", 'role': 'assistant'}], 'time': 24.91333508491516, 'trial': -1}\n", + " Evaluation dictionary: [\n", + " {\n", + " \"name\": \"Accuracy\",\n", + " \"description\": \"The solution must be correct and adhere strictly to mathematical principles and techniques appropriate for the problem.\",\n", + " \"accepted_values\": [\n", + " \"Correct\",\n", + " \"Minor errors\",\n", + " \"Major errors\",\n", + " \"Incorrect\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Conciseness\",\n", + " \"description\": \"The explanation and method provided should be direct and to the point, avoiding unnecessary steps or complexity.\",\n", + " \"accepted_values\": [\n", + " \"Very concise\",\n", + " \"Concise\",\n", + " \"Somewhat verbose\",\n", + " \"Verbose\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Relevance\",\n", + " \"description\": \"The content of the response must be relevant to the question posed and should address the specific problem requirements.\",\n", + " \"accepted_values\": [\n", + " \"Highly relevant\",\n", + " \"Relevant\",\n", + " \"Somewhat relevant\",\n", + " \"Not relevant\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Efficiency\",\n", + " \"description\": \"The solution should be derived in a time-effective manner, considering the complexity of the problem.\",\n", + " \"accepted_values\": [\n", + " \"Highly efficient\",\n", + " \"Efficient\",\n", + " \"Inefficient\",\n", + " \"Redundant\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Logic and Structure\",\n", + " \"description\": \"The reasoning should be logical and the information structured in a clear and understandable sequence.\",\n", + " \"accepted_values\": [\n", + " \"Exceptionally clear\",\n", + " \"Clear\",\n", + " \"Somewhat clear\",\n", + " \"Confusing\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Use of Resources\",\n", + " \"description\": \"The response should make appropriate and optimal use of external resources or tools (e.g., Python scripts) when necessary.\",\n", + " \"accepted_values\": [\n", + " \"Optimal\",\n", + " \"Appropriate\",\n", + " \"Underutilized\",\n", + " \"Overreliance\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Mathematical Notation\",\n", + " \"description\": \"The use of proper and standard mathematical notation in the solution and explanation.\",\n", + " \"accepted_values\": [\n", + " \"Excellent\",\n", + " \"Good\",\n", + " \"Adequate\",\n", + " \"Poor\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Explanation and Justification\",\n", + " \"description\": \"There should be a clear explanation, rationale, or justification for each step taken towards the solution.\",\n", + " \"accepted_values\": [\n", + " \"Thorough\",\n", + " \"Adequate\",\n", + " \"Insufficient\",\n", + " \"Missing\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Correctness of Answer Format\",\n", + " \"description\": \"The answer should be presented in the format requested in the problem (e.g., interval notation, simplified form).\",\n", + " \"accepted_values\": [\n", + " \"Perfectly formatted\",\n", + " \"Properly formatted\",\n", + " \"Slightly incorrect format\",\n", + " \"Improperly formatted\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Handling of Edge Cases\",\n", + " \"description\": \"The solution should correctly handle any special or edge cases that may arise in the problem.\",\n", + " \"accepted_values\": [\n", + " \"Complete\",\n", + " \"Most cases\",\n", + " \"Some cases\",\n", + " \"No consideration\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " }\n", + "]actual test case to evaluate: {'problem': 'Find $24^{-1} \\\\pmod{11^2}$. That is, find the residue $b$ for which $24b \\\\equiv 1\\\\pmod{11^2}$.\\n\\nExpress your answer as an integer from $0$ to $11^2-1$, inclusive.', 'level': 'Level 5', 'type': 'Number Theory', 'solution': 'Since $5 \\\\times 24 = 120 = 121 - 1$, it follows that $-5 \\\\times 24 \\\\equiv 1 \\\\pmod{121}$. Adding 121 to $-5$ to make it positive, we find $(-5 + 121) \\\\times 24 \\\\equiv 116 \\\\times 24 \\\\equiv 1 \\\\pmod{121}$, so it follows that the modular inverse of $24$ is $\\\\boxed{116}$ when taken modulo $121$.', 'problem_id': '5', 'response_with_ans': 'To find the modular inverse of 24 modulo 11^2, we can use the Extended Euclidean Algorithm. Here is a Python function to compute the modular inverse using this algorithm:\\n\\n```python\\ndef mod_inverse(a, m):\\n g, x, _ = extended_gcd(a, m)\\n if g != 1:\\n raise Exception(f\"{a} and {m} are not coprime.\")\\n return x % m\\n\\ndef extended_gcd(a, b):\\n if a == 0:\\n return b, 0, 1\\n else:\\n g, x, y = extended_gcd(b % a, a)\\n return g, y - (b // a) * x, x\\n```\\n\\nLet\\'s use the above function to find the modular inverse of 24 modulo 11^2:\\n\\n```python\\na = 24\\nm = 11**2\\nmod_inverse(a, m)\\n```\\n\\nI will execute the above code to find the modular inverse of 24 modulo 11^2.', 'round': 0, 'messages': [{'content': 'Find $24^{-1} \\\\pmod{11^2}$. That is, find the residue $b$ for which $24b \\\\equiv 1\\\\pmod{11^2}$.\\n\\nExpress your answer as an integer from $0$ to $11^2-1$, inclusive.', 'role': 'user'}, {'content': 'To find the modular inverse of 24 modulo 11^2, we can use the Extended Euclidean Algorithm. Here is a Python function to compute the modular inverse using this algorithm:\\n\\n```python\\ndef mod_inverse(a, m):\\n g, x, _ = extended_gcd(a, m)\\n if g != 1:\\n raise Exception(f\"{a} and {m} are not coprime.\")\\n return x % m\\n\\ndef extended_gcd(a, b):\\n if a == 0:\\n return b, 0, 1\\n else:\\n g, x, y = extended_gcd(b % a, a)\\n return g, y - (b // a) * x, x\\n```\\n\\nLet\\'s use the above function to find the modular inverse of 24 modulo 11^2:\\n\\n```python\\na = 24\\nm = 11**2\\nmod_inverse(a, m)\\n```\\n\\nI will execute the above code to find the modular inverse of 24 modulo 11^2.', 'role': 'assistant'}], 'time': 13.481226921081543, 'trial': -1}\n", "\n", "--------------------------------------------------------------------------------\n", + "\u001b[31m\n", + ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", "\u001b[33mquantifier\u001b[0m (to quantifier_user):\n", "\n", - "```json\n", "{\n", - " \"accuracy\": \"correct\",\n", - " \"completeness\": \"complete\",\n", - " \"efficiency\": \"efficient\",\n", - " \"methodology\": \"appropriate\",\n", - " \"clarity\": \"clear\",\n", - " \"use_of_language\": \"appropriate\"\n", + " \"Accuracy\": \"Correct\",\n", + " \"Conciseness\": \"Concise\",\n", + " \"Relevance\": \"Highly relevant\",\n", + " \"Efficiency\": \"Efficient\",\n", + " \"Logic and Structure\": \"Clear\",\n", + " \"Use of Resources\": \"Optimal\",\n", + " \"Mathematical Notation\": \"Good\",\n", + " \"Explanation and Justification\": \"Adequate\",\n", + " \"Correctness of Answer Format\": \"Perfectly formatted\",\n", + " \"Handling of Edge Cases\": \"Complete\"\n", "}\n", - "```\n", "\n", "--------------------------------------------------------------------------------\n", - "actual correctness: true\n", - "predicted coprrectness:\n", - " ```json\n", - "{\n", - " \"accuracy\": \"correct\",\n", - " \"completeness\": \"complete\",\n", - " \"efficiency\": \"efficient\",\n", - " \"methodology\": \"appropriate\",\n", - " \"clarity\": \"clear\",\n", - " \"use_of_language\": \"appropriate\"\n", - "}\n", - "```\n" + "actual correctness: True\n", + "predicted correctness:\n", + " {\n", + " \"Accuracy\": \"Correct\",\n", + " \"Conciseness\": \"Concise\",\n", + " \"Relevance\": \"Highly relevant\",\n", + " \"Efficiency\": \"Efficient\",\n", + " \"Logic and Structure\": \"Clear\",\n", + " \"Use of Resources\": \"Optimal\",\n", + " \"Mathematical Notation\": \"Good\",\n", + " \"Explanation and Justification\": \"Adequate\",\n", + " \"Correctness of Answer Format\": \"Perfectly formatted\",\n", + " \"Handling of Edge Cases\": \"Complete\"\n", + "}\n" ] } ], "source": [ - "test_case = \"../test/test_files/agenteval-in-out/samples/sample_test_case.json\"\n", - "quantifier_output = get_quantifier(test_case, criteria_file)\n", + "test_case = open(\"../test/test_files/agenteval-in-out/samples/sample_test_case.json\", \"r\").read()\n", + "test_case, ground_truth = remove_ground_truth(test_case)\n", + "quantifier_output = quantify_criteria(\n", + " llm_config={\"config_list\": config_list},\n", + " criteria=criteria,\n", + " task=task,\n", + " test_case=test_case,\n", + " ground_truth=ground_truth,\n", + ")\n", "print(\"actual correctness:\", quantifier_output[\"actual_success\"])\n", - "print(\"predicted coprrectness:\\n\", quantifier_output[\"estimated_performance\"])" + "print(\"predicted correctness:\\n\", quantifier_output[\"estimated_performance\"])" ] }, { @@ -677,28 +551,28 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "--2024-01-06 19:06:41-- https://github.com/julianakiseleva/autogen/raw/ddabd4f0e7c13a50e33cf8462e79358666371477/test/test_files/agenteval-in-out/prealgebra.zip\n", - "Resolving github.com (github.com)... 140.82.121.4\n", - "Connecting to github.com (github.com)|140.82.121.4|:443... connected.\n", + "--2024-05-08 17:42:25-- https://github.com/julianakiseleva/autogen/raw/ddabd4f0e7c13a50e33cf8462e79358666371477/test/test_files/agenteval-in-out/prealgebra.zip\n", + "Resolving github.com (github.com)... 140.82.116.3\n", + "Connecting to github.com (github.com)|140.82.116.3|:443... connected.\n", "HTTP request sent, awaiting response... 302 Found\n", "Location: https://raw.githubusercontent.com/julianakiseleva/autogen/ddabd4f0e7c13a50e33cf8462e79358666371477/test/test_files/agenteval-in-out/prealgebra.zip [following]\n", - "--2024-01-06 19:06:41-- https://raw.githubusercontent.com/julianakiseleva/autogen/ddabd4f0e7c13a50e33cf8462e79358666371477/test/test_files/agenteval-in-out/prealgebra.zip\n", - "Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.109.133, 185.199.108.133, 185.199.110.133, ...\n", + "--2024-05-08 17:42:25-- https://raw.githubusercontent.com/julianakiseleva/autogen/ddabd4f0e7c13a50e33cf8462e79358666371477/test/test_files/agenteval-in-out/prealgebra.zip\n", + "Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.109.133, 185.199.110.133, 185.199.111.133, ...\n", "Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.109.133|:443... connected.\n", "HTTP request sent, awaiting response... 200 OK\n", "Length: 28567 (28K) [application/zip]\n", "Saving to: β€˜prealgebra.zip’\n", "\n", - "prealgebra.zip 100%[===================>] 27.90K --.-KB/s in 0.005s \n", + "prealgebra.zip 100%[===================>] 27.90K --.-KB/s in 0s \n", "\n", - "2024-01-06 19:06:41 (5.85 MB/s) - β€˜prealgebra.zip’ saved [28567/28567]\n", + "2024-05-08 17:42:25 (63.0 MB/s) - β€˜prealgebra.zip’ saved [28567/28567]\n", "\n", "Archive: prealgebra.zip\n", "warning: skipped \"../\" path component(s) in ../prealgebra/\n", @@ -763,7 +637,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 8, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -776,358 +650,285 @@ "name": "stdout", "output_type": "stream", "text": [ - "actual label for this case: true\n", "\u001b[33mquantifier_user\u001b[0m (to quantifier):\n", "\n", "Task: Math problem solving.\n", - "Task description: Given any question, the system needs to solve the problem as consisely and accurately as possible\n", - "Task successful example: {\n", - " \"problem\": \"What is the sum of all the distinct positive two-digit factors of 144?\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Number Theory\",\n", - " \"solution\": \"Prime factorize $144=2^4\\\\cdot3^2$. The sum of the positive two-digit factors of 144 is $2^4+2\\\\cdot3^2+2^2\\\\cdot3+2^2\\\\cdot3^2+2^3\\\\cdot3+2^3\\\\cdot3^2+2^4\\\\cdot3=\\\\boxed{226}.$\",\n", - " \"problem_id\": \"0\",\n", - " \"response_with_ans\": \"To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere's a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\\\"The sum of all the distinct positive two-digit factors of 144 is:\\\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.\",\n", - " \"round\": 0,\n", - " \"messages\": [\n", - " {\n", - " \"content\": \"What is the sum of all the distinct positive two-digit factors of 144?\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere's a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\\\"The sum of all the distinct positive two-digit factors of 144 is:\\\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 11.140539407730103,\n", - " \"trial\": -1\n", - "}\n", - "\n", - "Task failed example: {\n", - " \"problem\": \"Find all $x$ that satisfy the inequality $(2x+10)(x+3)<(3x+9)(x+8)$. Express your answer in interval notation.\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Algebra\",\n", - " \"solution\": \"We have \\\\begin{align*} (2x+10)(x+3)&<(3x+9)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)&<3(x+3)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)-3(x+3)(x+8)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (2x+10-(3x+24))(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (-x-14)(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (x+14)(x+3)&>0.\\n\\\\end{align*} This inequality is satisfied if and only if $(x+14)$ and $(x+3)$ are either both positive or both negative. Both factors are positive for $x>-3$ and both factors are negative for $x<-14$. When $-14 0$ and then find intervals where the inequality holds true.\\n\\nLet's first find the factorized form of the quadratic:\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"I apologize for the confusion in my previous message. Let's find the factorized form of the quadratic using Python code.\\n\\n```python\\nfrom sympy import symbols, factor\\r\\nx = symbols('x')\\r\\nfactorized = factor(x**2 + 17*x + 42)\\r\\nfactorized\\r\\n```\\n\\nPlease run the Python code above to find the factorized form of the quadratic, and we can proceed to determine the intervals where the inequality holds true.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 24.91333508491516,\n", - " \"trial\": -1\n", - "}\n", - "\n", - "Evaluation dictionary: {\n", - " \"Problem Interpretation\": {\n", - " \"description\": \"Ability to correctly interpret the problem.\",\n", - " \"accepted_values\": [\"completely off\", \"slightly relevant\", \"relevant\", \"mostly accurate\", \"completely accurate\"]\n", - " },\n", - " \"Mathematical Methodology\": {\n", - " \"description\": \"Adequacy of the chosen mathematical or algorithmic methodology for the question\",\n", - " \"accepted_values\": [\"inappropriate\", \"barely adequate\", \"adequate\", \"mostly effective\", \"completely effective\"]\n", - " },\n", - " \"Calculation Correctness\": {\n", - " \"description\": \"Accuracy of calculations made and solutions given\",\n", - " \"accepted_values\": [\"completely incorrect\", \"mostly incorrect\", \"neither\", \"mostly correct\", \"completely correct\"]\n", - " },\n", - " \"Explanation Clarity\": {\n", - " \"description\": \"Clarity and comprehensibility of explanations, including language use and structure\",\n", - " \"accepted_values\": [\"not at all clear\", \"slightly clear\", \"moderately clear\", \"very clear\", \"completely clear\"]\n", - " },\n", - " \"Code Efficiency\": {\n", - " \"description\": \"Quality of code in terms of efficiency and elegance\",\n", - " \"accepted_values\": [\"not at all efficient\", \"slightly efficient\", \"moderately efficient\", \"very efficient\", \"extremely efficient\"]\n", - " },\n", - " \"Code Correctness\": {\n", - " \"description\": \"Correctness of the provided code\",\n", - " \"accepted_values\": [\"completely incorrect\", \"mostly incorrect\", \"partly correct\", \"mostly correct\", \"completely correct\"]\n", - " }\n", + " Task description: Given any question, the system needs to solve the problem as consisely and accurately as possible\n", + " Task successful example: {'problem': 'What is the sum of all the distinct positive two-digit factors of 144?', 'level': 'Level 5', 'type': 'Number Theory', 'solution': 'Prime factorize $144=2^4\\\\cdot3^2$. The sum of the positive two-digit factors of 144 is $2^4+2\\\\cdot3^2+2^2\\\\cdot3+2^2\\\\cdot3^2+2^3\\\\cdot3+2^3\\\\cdot3^2+2^4\\\\cdot3=\\\\boxed{226}.$', 'problem_id': '0', 'response_with_ans': 'To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere\\'s a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\"The sum of all the distinct positive two-digit factors of 144 is:\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.', 'round': 0, 'messages': [{'content': 'What is the sum of all the distinct positive two-digit factors of 144?', 'role': 'user'}, {'content': 'To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere\\'s a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\"The sum of all the distinct positive two-digit factors of 144 is:\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.', 'role': 'assistant'}], 'time': 11.140539407730103, 'trial': -1}\n", + " Task failed example: {'problem': 'Find all $x$ that satisfy the inequality $(2x+10)(x+3)<(3x+9)(x+8)$. Express your answer in interval notation.', 'level': 'Level 5', 'type': 'Algebra', 'solution': 'We have \\\\begin{align*} (2x+10)(x+3)&<(3x+9)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)&<3(x+3)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)-3(x+3)(x+8)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (2x+10-(3x+24))(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (-x-14)(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (x+14)(x+3)&>0.\\n\\\\end{align*} This inequality is satisfied if and only if $(x+14)$ and $(x+3)$ are either both positive or both negative. Both factors are positive for $x>-3$ and both factors are negative for $x<-14$. When $-14 0$ and then find intervals where the inequality holds true.\\n\\nLet's first find the factorized form of the quadratic:\", 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': \"I apologize for the confusion in my previous message. Let's find the factorized form of the quadratic using Python code.\\n\\n```python\\nfrom sympy import symbols, factor\\r\\nx = symbols('x')\\r\\nfactorized = factor(x**2 + 17*x + 42)\\r\\nfactorized\\r\\n```\\n\\nPlease run the Python code above to find the factorized form of the quadratic, and we can proceed to determine the intervals where the inequality holds true.\", 'role': 'assistant'}], 'time': 24.91333508491516, 'trial': -1}\n", + " Evaluation dictionary: [\n", + " {\n", + " \"name\": \"Problem Interpretation\",\n", + " \"description\": \"Ability to correctly interpret the problem.\",\n", + " \"accepted_values\": [\n", + " \"completely off\",\n", + " \"slightly relevant\",\n", + " \"relevant\",\n", + " \"mostly accurate\",\n", + " \"completely accurate\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Mathematical Methodology\",\n", + " \"description\": \"Adequacy of the chosen mathematical or algorithmic methodology for the question\",\n", + " \"accepted_values\": [\n", + " \"inappropriate\",\n", + " \"barely adequate\",\n", + " \"adequate\",\n", + " \"mostly effective\",\n", + " \"completely effective\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Calculation Correctness\",\n", + " \"description\": \"Accuracy of calculations made and solutions given\",\n", + " \"accepted_values\": [\n", + " \"completely incorrect\",\n", + " \"mostly incorrect\",\n", + " \"neither\",\n", + " \"mostly correct\",\n", + " \"completely correct\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Explanation Clarity\",\n", + " \"description\": \"Clarity and comprehensibility of explanations, including language use and structure\",\n", + " \"accepted_values\": [\n", + " \"not at all clear\",\n", + " \"slightly clear\",\n", + " \"moderately clear\",\n", + " \"very clear\",\n", + " \"completely clear\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Code Efficiency\",\n", + " \"description\": \"Quality of code in terms of efficiency and elegance\",\n", + " \"accepted_values\": [\n", + " \"not at all efficient\",\n", + " \"slightly efficient\",\n", + " \"moderately efficient\",\n", + " \"very efficient\",\n", + " \"extremely efficient\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Code Correctness\",\n", + " \"description\": \"Correctness of the provided code\",\n", + " \"accepted_values\": [\n", + " \"completely incorrect\",\n", + " \"mostly incorrect\",\n", + " \"partly correct\",\n", + " \"mostly correct\",\n", + " \"completely correct\"\n", + " ],\n", + " \"sub_criteria\": []\n", " }\n", - "actual test case to evaluate: {\n", - " \"problem\": \"Amaretta's birthday is July 27, and her brother Enzo's birthday is September 3. Every year, Amaretta and Enzo celebrate by eating cake every day from Amaretta's birthday through Enzo's birthday (including both birthdays). If they did this for the first time in 2008, how many cake-eating days will they have observed by the end of 2016?\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Prealgebra\",\n", - " \"solution\": \"There are $39$ cake-eating days each year: the last $5$ days of July, all $31$ days of August, and the first $3$ days of September.\\n\\nThere are $9$ years in the list $$2008,2009,2010,2011,2012,2013,2014,2015,2016.$$ Besides listing them out, we can also see this by subtracting $2007$ from each year, which gives us the list $1,2,3,4,5,6,7,8,9$ (which clearly has $9$ entries).\\n\\n$39$ cake-eating days each year for $9$ years make $39\\\\cdot 9 = \\\\boxed{351}$ days in total.\",\n", - " \"problem_id\": \"3\",\n", - " \"response_with_ans\": \"To calculate the total number of cake-eating days, we will first calculate the number of days between Amaretta's birthday and Enzo's birthday in a non-leap year and in a leap year. Then, we will count the number of leap years and non-leap years in the given range (2008-2016). Finally, we will sum the total number of days for each type of year for both non-leap and leap years.\\n\\nPlease run the following Python code:\\n\\n```python\\nfrom datetime import date\\n\\namaretta_birthday = date(2008, 7, 27)\\nenzo_birthday = date(2008, 9, 3)\\n\\nnormal_year_days = (enzo_birthday - amaretta_birthday).days + 1\\nleap_year_days = normal_year_days + 1\\n\\nleap_years = [year for year in range(2008, 2017) if year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)]\\nnon_leap_years_count = 9 - len(leap_years)\\n\\ntotal_cake_eating_days = non_leap_years_count * normal_year_days + len(leap_years) * leap_year_days\\nprint(total_cake_eating_days)\\n```\\n\",\n", - " \"round\": 0,\n", - " \"messages\": [\n", - " {\n", - " \"content\": \"Amaretta's birthday is July 27, and her brother Enzo's birthday is September 3. Every year, Amaretta and Enzo celebrate by eating cake every day from Amaretta's birthday through Enzo's birthday (including both birthdays). If they did this for the first time in 2008, how many cake-eating days will they have observed by the end of 2016?\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"To calculate the total number of cake-eating days, we will first calculate the number of days between Amaretta's birthday and Enzo's birthday in a non-leap year and in a leap year. Then, we will count the number of leap years and non-leap years in the given range (2008-2016). Finally, we will sum the total number of days for each type of year for both non-leap and leap years.\\n\\nPlease run the following Python code:\\n\\n```python\\nfrom datetime import date\\n\\namaretta_birthday = date(2008, 7, 27)\\nenzo_birthday = date(2008, 9, 3)\\n\\nnormal_year_days = (enzo_birthday - amaretta_birthday).days + 1\\nleap_year_days = normal_year_days + 1\\n\\nleap_years = [year for year in range(2008, 2017) if year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)]\\nnon_leap_years_count = 9 - len(leap_years)\\n\\ntotal_cake_eating_days = non_leap_years_count * normal_year_days + len(leap_years) * leap_year_days\\nprint(total_cake_eating_days)\\n```\\n\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 15.208062410354614,\n", - " \"trial\": -1\n", - "}\n", + "]actual test case to evaluate: {'problem': 'Bekah has exactly three brass house number digits: 2, 3 and 5. How many distinct numbers can she form using one or more of the digits?', 'level': 'Level 5', 'type': 'Prealgebra', 'solution': 'If Bekah uses only one digit, she can form three numbers. If she uses two digits, she has three choices for the tens place and two for the units, so she can form six numbers. Finally, if Bekah uses all three digits, she has three choices for the hundreds place, two for the tens, and one for the units, so she can form six numbers. Thus, Bekah can form $3 + 6 + 6 = \\\\boxed{15}$ distinct numbers.', 'problem_id': '11', 'response_with_ans': 'To find the answer, we\\'ll consider all possible combinations that Bekah can make using the three digits she has:\\n1. Single-digit numbers: 2, 3, and 5\\n2. Two-digits numbers using the digits: 23, 25, 32, 35, 52, and 53\\n3. Three-digits number using all the digits: 235, 253, 325, 352, 523, and 532\\n\\nLet\\'s calculate the number of distinct numbers Bekah can form using one or more of the digits with Python code:\\n\\n```python\\nfrom itertools import permutations\\n\\ndigits = [2, 3, 5]\\n\\n# Single-digit numbers\\none_digit_numbers = set(digits)\\n\\n# Two-digits numbers\\ntwo_digit_numbers = set(int(\\'\\'.join(map(str, p))) for p in permutations(digits, 2))\\n\\n# Three-digits numbers\\nthree_digit_numbers = set(int(\\'\\'.join(map(str, p))) for p in permutations(digits, 3))\\n\\ndistinct_numbers = one_digit_numbers | two_digit_numbers | three_digit_numbers\\ntotal_distinct_numbers = len(distinct_numbers)\\n\\nprint(\"Distinct numbers:\", distinct_numbers)\\nprint(\"Total distinct numbers:\", total_distinct_numbers)\\n```\\n\\nPlease run the above Python code to calculate the total number of distinct numbers Bekah can form using one or more of the digits.', 'round': 0, 'messages': [{'content': 'Bekah has exactly three brass house number digits: 2, 3 and 5. How many distinct numbers can she form using one or more of the digits?', 'role': 'user'}, {'content': 'To find the answer, we\\'ll consider all possible combinations that Bekah can make using the three digits she has:\\n1. Single-digit numbers: 2, 3, and 5\\n2. Two-digits numbers using the digits: 23, 25, 32, 35, 52, and 53\\n3. Three-digits number using all the digits: 235, 253, 325, 352, 523, and 532\\n\\nLet\\'s calculate the number of distinct numbers Bekah can form using one or more of the digits with Python code:\\n\\n```python\\nfrom itertools import permutations\\n\\ndigits = [2, 3, 5]\\n\\n# Single-digit numbers\\none_digit_numbers = set(digits)\\n\\n# Two-digits numbers\\ntwo_digit_numbers = set(int(\\'\\'.join(map(str, p))) for p in permutations(digits, 2))\\n\\n# Three-digits numbers\\nthree_digit_numbers = set(int(\\'\\'.join(map(str, p))) for p in permutations(digits, 3))\\n\\ndistinct_numbers = one_digit_numbers | two_digit_numbers | three_digit_numbers\\ntotal_distinct_numbers = len(distinct_numbers)\\n\\nprint(\"Distinct numbers:\", distinct_numbers)\\nprint(\"Total distinct numbers:\", total_distinct_numbers)\\n```\\n\\nPlease run the above Python code to calculate the total number of distinct numbers Bekah can form using one or more of the digits.', 'role': 'assistant'}], 'time': 15.620970249176025, 'trial': -1}\n", "\n", "--------------------------------------------------------------------------------\n", + "\u001b[31m\n", + ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", "\u001b[33mquantifier\u001b[0m (to quantifier_user):\n", "\n", "{\n", - " \"Problem Interpretation\": \"completely accurate\",\n", - " \"Mathematical Methodology\": \"completely effective\",\n", - " \"Calculation Correctness\": \"completely correct\",\n", - " \"Explanation Clarity\": \"very clear\",\n", - " \"Code Efficiency\": \"very efficient\",\n", - " \"Code Correctness\": \"completely correct\"\n", + " \"Problem Interpretation\": \"completely accurate\",\n", + " \"Mathematical Methodology\": \"completely effective\",\n", + " \"Calculation Correctness\": \"completely correct\",\n", + " \"Explanation Clarity\": \"very clear\",\n", + " \"Code Efficiency\": \"very efficient\",\n", + " \"Code Correctness\": \"completely correct\"\n", "}\n", "\n", "--------------------------------------------------------------------------------\n", - "actual label for this case: true\n", "\u001b[33mquantifier_user\u001b[0m (to quantifier):\n", "\n", "Task: Math problem solving.\n", - "Task description: Given any question, the system needs to solve the problem as consisely and accurately as possible\n", - "Task successful example: {\n", - " \"problem\": \"What is the sum of all the distinct positive two-digit factors of 144?\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Number Theory\",\n", - " \"solution\": \"Prime factorize $144=2^4\\\\cdot3^2$. The sum of the positive two-digit factors of 144 is $2^4+2\\\\cdot3^2+2^2\\\\cdot3+2^2\\\\cdot3^2+2^3\\\\cdot3+2^3\\\\cdot3^2+2^4\\\\cdot3=\\\\boxed{226}.$\",\n", - " \"problem_id\": \"0\",\n", - " \"response_with_ans\": \"To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere's a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\\\"The sum of all the distinct positive two-digit factors of 144 is:\\\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.\",\n", - " \"round\": 0,\n", - " \"messages\": [\n", - " {\n", - " \"content\": \"What is the sum of all the distinct positive two-digit factors of 144?\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere's a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\\\"The sum of all the distinct positive two-digit factors of 144 is:\\\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 11.140539407730103,\n", - " \"trial\": -1\n", - "}\n", - "\n", - "Task failed example: {\n", - " \"problem\": \"Find all $x$ that satisfy the inequality $(2x+10)(x+3)<(3x+9)(x+8)$. Express your answer in interval notation.\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Algebra\",\n", - " \"solution\": \"We have \\\\begin{align*} (2x+10)(x+3)&<(3x+9)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)&<3(x+3)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)-3(x+3)(x+8)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (2x+10-(3x+24))(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (-x-14)(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (x+14)(x+3)&>0.\\n\\\\end{align*} This inequality is satisfied if and only if $(x+14)$ and $(x+3)$ are either both positive or both negative. Both factors are positive for $x>-3$ and both factors are negative for $x<-14$. When $-14 0$ and then find intervals where the inequality holds true.\\n\\nLet's first find the factorized form of the quadratic:\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"I apologize for the confusion in my previous message. Let's find the factorized form of the quadratic using Python code.\\n\\n```python\\nfrom sympy import symbols, factor\\r\\nx = symbols('x')\\r\\nfactorized = factor(x**2 + 17*x + 42)\\r\\nfactorized\\r\\n```\\n\\nPlease run the Python code above to find the factorized form of the quadratic, and we can proceed to determine the intervals where the inequality holds true.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 24.91333508491516,\n", - " \"trial\": -1\n", - "}\n", - "\n", - "Evaluation dictionary: {\n", - " \"Problem Interpretation\": {\n", - " \"description\": \"Ability to correctly interpret the problem.\",\n", - " \"accepted_values\": [\"completely off\", \"slightly relevant\", \"relevant\", \"mostly accurate\", \"completely accurate\"]\n", - " },\n", - " \"Mathematical Methodology\": {\n", - " \"description\": \"Adequacy of the chosen mathematical or algorithmic methodology for the question\",\n", - " \"accepted_values\": [\"inappropriate\", \"barely adequate\", \"adequate\", \"mostly effective\", \"completely effective\"]\n", - " },\n", - " \"Calculation Correctness\": {\n", - " \"description\": \"Accuracy of calculations made and solutions given\",\n", - " \"accepted_values\": [\"completely incorrect\", \"mostly incorrect\", \"neither\", \"mostly correct\", \"completely correct\"]\n", - " },\n", - " \"Explanation Clarity\": {\n", - " \"description\": \"Clarity and comprehensibility of explanations, including language use and structure\",\n", - " \"accepted_values\": [\"not at all clear\", \"slightly clear\", \"moderately clear\", \"very clear\", \"completely clear\"]\n", - " },\n", - " \"Code Efficiency\": {\n", - " \"description\": \"Quality of code in terms of efficiency and elegance\",\n", - " \"accepted_values\": [\"not at all efficient\", \"slightly efficient\", \"moderately efficient\", \"very efficient\", \"extremely efficient\"]\n", - " },\n", - " \"Code Correctness\": {\n", - " \"description\": \"Correctness of the provided code\",\n", - " \"accepted_values\": [\"completely incorrect\", \"mostly incorrect\", \"partly correct\", \"mostly correct\", \"completely correct\"]\n", - " }\n", + " Task description: Given any question, the system needs to solve the problem as consisely and accurately as possible\n", + " Task successful example: {'problem': 'What is the sum of all the distinct positive two-digit factors of 144?', 'level': 'Level 5', 'type': 'Number Theory', 'solution': 'Prime factorize $144=2^4\\\\cdot3^2$. The sum of the positive two-digit factors of 144 is $2^4+2\\\\cdot3^2+2^2\\\\cdot3+2^2\\\\cdot3^2+2^3\\\\cdot3+2^3\\\\cdot3^2+2^4\\\\cdot3=\\\\boxed{226}.$', 'problem_id': '0', 'response_with_ans': 'To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere\\'s a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\"The sum of all the distinct positive two-digit factors of 144 is:\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.', 'round': 0, 'messages': [{'content': 'What is the sum of all the distinct positive two-digit factors of 144?', 'role': 'user'}, {'content': 'To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere\\'s a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\"The sum of all the distinct positive two-digit factors of 144 is:\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.', 'role': 'assistant'}], 'time': 11.140539407730103, 'trial': -1}\n", + " Task failed example: {'problem': 'Find all $x$ that satisfy the inequality $(2x+10)(x+3)<(3x+9)(x+8)$. Express your answer in interval notation.', 'level': 'Level 5', 'type': 'Algebra', 'solution': 'We have \\\\begin{align*} (2x+10)(x+3)&<(3x+9)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)&<3(x+3)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)-3(x+3)(x+8)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (2x+10-(3x+24))(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (-x-14)(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (x+14)(x+3)&>0.\\n\\\\end{align*} This inequality is satisfied if and only if $(x+14)$ and $(x+3)$ are either both positive or both negative. Both factors are positive for $x>-3$ and both factors are negative for $x<-14$. When $-14 0$ and then find intervals where the inequality holds true.\\n\\nLet's first find the factorized form of the quadratic:\", 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': \"I apologize for the confusion in my previous message. Let's find the factorized form of the quadratic using Python code.\\n\\n```python\\nfrom sympy import symbols, factor\\r\\nx = symbols('x')\\r\\nfactorized = factor(x**2 + 17*x + 42)\\r\\nfactorized\\r\\n```\\n\\nPlease run the Python code above to find the factorized form of the quadratic, and we can proceed to determine the intervals where the inequality holds true.\", 'role': 'assistant'}], 'time': 24.91333508491516, 'trial': -1}\n", + " Evaluation dictionary: [\n", + " {\n", + " \"name\": \"Problem Interpretation\",\n", + " \"description\": \"Ability to correctly interpret the problem.\",\n", + " \"accepted_values\": [\n", + " \"completely off\",\n", + " \"slightly relevant\",\n", + " \"relevant\",\n", + " \"mostly accurate\",\n", + " \"completely accurate\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Mathematical Methodology\",\n", + " \"description\": \"Adequacy of the chosen mathematical or algorithmic methodology for the question\",\n", + " \"accepted_values\": [\n", + " \"inappropriate\",\n", + " \"barely adequate\",\n", + " \"adequate\",\n", + " \"mostly effective\",\n", + " \"completely effective\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Calculation Correctness\",\n", + " \"description\": \"Accuracy of calculations made and solutions given\",\n", + " \"accepted_values\": [\n", + " \"completely incorrect\",\n", + " \"mostly incorrect\",\n", + " \"neither\",\n", + " \"mostly correct\",\n", + " \"completely correct\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Explanation Clarity\",\n", + " \"description\": \"Clarity and comprehensibility of explanations, including language use and structure\",\n", + " \"accepted_values\": [\n", + " \"not at all clear\",\n", + " \"slightly clear\",\n", + " \"moderately clear\",\n", + " \"very clear\",\n", + " \"completely clear\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Code Efficiency\",\n", + " \"description\": \"Quality of code in terms of efficiency and elegance\",\n", + " \"accepted_values\": [\n", + " \"not at all efficient\",\n", + " \"slightly efficient\",\n", + " \"moderately efficient\",\n", + " \"very efficient\",\n", + " \"extremely efficient\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Code Correctness\",\n", + " \"description\": \"Correctness of the provided code\",\n", + " \"accepted_values\": [\n", + " \"completely incorrect\",\n", + " \"mostly incorrect\",\n", + " \"partly correct\",\n", + " \"mostly correct\",\n", + " \"completely correct\"\n", + " ],\n", + " \"sub_criteria\": []\n", " }\n", - "actual test case to evaluate: {\n", - " \"problem\": \"Two numbers are said to be 'relatively prime' if their greatest common factor is 1. How many integers greater than 10 and less than 30 are relatively prime with 28?\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Prealgebra\",\n", - " \"solution\": \"Since $28=2^2\\\\cdot 7$, a positive integer is relatively prime with $28$ if and only if it contains neither $2$ nor $7$ in its prime factorization. In other words, we want to count the number of integers between $11$ and $29$ inclusive which are divisible by neither $2$ nor $7$.\\n\\nAll of the odd numbers are not divisible by 2; there are 10 such numbers. The only one of these that is divisible by 7 is 21, so there are $10- 1 =\\\\boxed{9}$ numbers between 10 and 30 that are relatively prime with 28.\",\n", - " \"problem_id\": \"12\",\n", - " \"response_with_ans\": \"I will find the relatively prime numbers with 28 in the range greater than 10 and less than 30 using Python code. I will use the math.gcd function and a loop to check each number in the specified range.\\n\\n```python\\nimport math\\n\\ncount = 0\\nrelatively_prime_numbers = []\\nfor number in range(11, 30):\\n if math.gcd(28, number) == 1:\\n relatively_prime_numbers.append(number)\\n count += 1\\n\\nprint(f\\\"Relatively prime numbers with 28: {relatively_prime_numbers}\\\")\\nprint(f\\\"Number of relatively prime numbers with 28: {count}\\\")\\n```\\nExecute the above code to find the integers relatively prime with 28 and their count.\",\n", - " \"round\": 0,\n", - " \"messages\": [\n", - " {\n", - " \"content\": \"Two numbers are said to be 'relatively prime' if their greatest common factor is 1. How many integers greater than 10 and less than 30 are relatively prime with 28?\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"I will find the relatively prime numbers with 28 in the range greater than 10 and less than 30 using Python code. I will use the math.gcd function and a loop to check each number in the specified range.\\n\\n```python\\nimport math\\n\\ncount = 0\\nrelatively_prime_numbers = []\\nfor number in range(11, 30):\\n if math.gcd(28, number) == 1:\\n relatively_prime_numbers.append(number)\\n count += 1\\n\\nprint(f\\\"Relatively prime numbers with 28: {relatively_prime_numbers}\\\")\\nprint(f\\\"Number of relatively prime numbers with 28: {count}\\\")\\n```\\nExecute the above code to find the integers relatively prime with 28 and their count.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 6.9820802211761475,\n", - " \"trial\": -1\n", - "}\n", + "]actual test case to evaluate: {'problem': 'What is $.0\\\\overline{3} \\\\div .\\\\overline{03}$? Express your answer as a mixed number.', 'level': 'Level 5', 'type': 'Prealgebra', 'solution': 'It is almost always easier to use fractions than decimals when dividing. So the first task is to convert these repeating decimals to fractions. First, $.0\\\\overline{3}$: \\\\[\\n10 \\\\cdot .0\\\\overline{3} = .\\\\overline{3} = \\\\frac{1}{3}\\\\\\\\\\n\\\\Rightarrow .0\\\\overline{3} = \\\\frac{1}{3} \\\\div 10 = \\\\frac{1}{3} \\\\cdot \\\\frac{1}{10} = \\\\frac{1}{30}.\\n\\\\]Next, $.\\\\overline{03}$: \\\\[\\n99 \\\\cdot .\\\\overline{03} = (100-1) \\\\cdot .\\\\overline{03} = 3.\\\\overline{03} - .\\\\overline{03} = 3\\\\\\\\\\n\\\\Rightarrow .\\\\overline{03} = \\\\frac{3}{99} = \\\\frac{3}{3 \\\\cdot 33} = \\\\frac{1}{33}.\\n\\\\]We now have the tools to make our calculation: \\\\begin{align*}\\n.0\\\\overline{3} \\\\div .\\\\overline{03} &= \\\\frac{1}{30} \\\\div \\\\frac{1}{33}= \\\\frac{1}{30} \\\\cdot \\\\frac{33}{1}\\\\\\\\\\n&= \\\\frac{33}{30} = \\\\frac{3 \\\\cdot 11}{3 \\\\cdot 10} = \\\\frac{11}{10}\\\\\\\\\\n&= \\\\frac{10+1}{10} = \\\\boxed{1\\\\frac{1}{10}}.\\n\\\\end{align*}', 'problem_id': '15', 'response_with_ans': \"Now, let's simplify the fraction:\\n\\n$\\\\frac{33}{3} = 11$\\n\\nSo, the result of the division is:\\n\\n$.0\\\\overline{3} \\\\div .\\\\overline{03} = 11$\\n\\nSince there is no remainder or mixed number component, the final answer is 11.\\n\\nTERMINATE\", 'round': 3, 'messages': [{'content': 'What is $.0\\\\overline{3} \\\\div .\\\\overline{03}$? Express your answer as a mixed number.', 'role': 'user'}, {'content': \"To solve the problem, let's first convert the repeating decimal numbers into fractions.\\n\\n1. Convert $0.\\\\overline{3}$ to a fraction:\\n\\nLet x = $0.\\\\overline{3}$\\nThen, 10x = $3.\\\\overline{3}$\\nSubtracting the two equations, we get:\\n9x = 3\\nx = $\\\\frac{1}{3}$\\n\\nSo, $0.\\\\overline{3}$ can be written as $\\\\frac{1}{3}$.\\n\\n2. Convert $.\\\\overline{03}$ to a fraction:\\n\\nLet y = $.\\\\overline{03}$\\nThen, 100y = $3.\\\\overline{03}$\\nSubtracting the two equations, we get:\\n99y = 3\\ny = $\\\\frac{1}{33}$\\n\\nSo, $.\\\\overline{03}$ can be written as $\\\\frac{1}{33}$.\\n\\nNow that we have the fractions, we can compute: \\n$(\\\\frac{1}{3}) \\\\div (\\\\frac{1}{33})$\\nTo divide fractions, we multiply the first one by the reciprocal of the second one:\\n\\n$(\\\\frac{1}{3}) \\\\times (\\\\frac{33}{1})$\", 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': \"Let's compute the product of the fractions:\\n\\n$\\\\frac{1}{3} \\\\times \\\\frac{33}{1} = \\\\frac{1 \\\\times 33}{3 \\\\times 1}$\", 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': \"Now, let's solve the multiplication in the numerator and the denominator:\\n\\n$\\\\frac{1 \\\\times 33}{3 \\\\times 1} = \\\\frac{33}{3}$\", 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': \"Now, let's simplify the fraction:\\n\\n$\\\\frac{33}{3} = 11$\\n\\nSo, the result of the division is:\\n\\n$.0\\\\overline{3} \\\\div .\\\\overline{03} = 11$\\n\\nSince there is no remainder or mixed number component, the final answer is 11.\\n\\nTERMINATE\", 'role': 'assistant'}], 'time': 34.40860724449158, 'trial': -1}\n", "\n", "--------------------------------------------------------------------------------\n", + "\u001b[31m\n", + ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", "\u001b[33mquantifier\u001b[0m (to quantifier_user):\n", "\n", "{\n", - " \"Problem Interpretation\": \"completely accurate\",\n", - " \"Mathematical Methodology\": \"completely effective\",\n", - " \"Calculation Correctness\": \"completely correct\",\n", - " \"Explanation Clarity\": \"very clear\",\n", - " \"Code Efficiency\": \"moderately efficient\",\n", - " \"Code Correctness\": \"completely correct\"\n", + " \"Problem Interpretation\": \"completely accurate\",\n", + " \"Mathematical Methodology\": \"completely effective\",\n", + " \"Calculation Correctness\": \"completely incorrect\",\n", + " \"Explanation Clarity\": \"moderately clear\",\n", + " \"Code Efficiency\": \"not applicable\",\n", + " \"Code Correctness\": \"not applicable\"\n", "}\n", "\n", "--------------------------------------------------------------------------------\n", - "actual label for this case: true\n", "\u001b[33mquantifier_user\u001b[0m (to quantifier):\n", "\n", "Task: Math problem solving.\n", - "Task description: Given any question, the system needs to solve the problem as consisely and accurately as possible\n", - "Task successful example: {\n", - " \"problem\": \"What is the sum of all the distinct positive two-digit factors of 144?\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Number Theory\",\n", - " \"solution\": \"Prime factorize $144=2^4\\\\cdot3^2$. The sum of the positive two-digit factors of 144 is $2^4+2\\\\cdot3^2+2^2\\\\cdot3+2^2\\\\cdot3^2+2^3\\\\cdot3+2^3\\\\cdot3^2+2^4\\\\cdot3=\\\\boxed{226}.$\",\n", - " \"problem_id\": \"0\",\n", - " \"response_with_ans\": \"To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere's a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\\\"The sum of all the distinct positive two-digit factors of 144 is:\\\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.\",\n", - " \"round\": 0,\n", - " \"messages\": [\n", - " {\n", - " \"content\": \"What is the sum of all the distinct positive two-digit factors of 144?\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere's a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\\\"The sum of all the distinct positive two-digit factors of 144 is:\\\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 11.140539407730103,\n", - " \"trial\": -1\n", - "}\n", - "\n", - "Task failed example: {\n", - " \"problem\": \"Find all $x$ that satisfy the inequality $(2x+10)(x+3)<(3x+9)(x+8)$. Express your answer in interval notation.\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Algebra\",\n", - " \"solution\": \"We have \\\\begin{align*} (2x+10)(x+3)&<(3x+9)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)&<3(x+3)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)-3(x+3)(x+8)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (2x+10-(3x+24))(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (-x-14)(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (x+14)(x+3)&>0.\\n\\\\end{align*} This inequality is satisfied if and only if $(x+14)$ and $(x+3)$ are either both positive or both negative. Both factors are positive for $x>-3$ and both factors are negative for $x<-14$. When $-14 0$ and then find intervals where the inequality holds true.\\n\\nLet's first find the factorized form of the quadratic:\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"I apologize for the confusion in my previous message. Let's find the factorized form of the quadratic using Python code.\\n\\n```python\\nfrom sympy import symbols, factor\\r\\nx = symbols('x')\\r\\nfactorized = factor(x**2 + 17*x + 42)\\r\\nfactorized\\r\\n```\\n\\nPlease run the Python code above to find the factorized form of the quadratic, and we can proceed to determine the intervals where the inequality holds true.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 24.91333508491516,\n", - " \"trial\": -1\n", - "}\n", - "\n", - "Evaluation dictionary: {\n", - " \"Problem Interpretation\": {\n", - " \"description\": \"Ability to correctly interpret the problem.\",\n", - " \"accepted_values\": [\"completely off\", \"slightly relevant\", \"relevant\", \"mostly accurate\", \"completely accurate\"]\n", - " },\n", - " \"Mathematical Methodology\": {\n", - " \"description\": \"Adequacy of the chosen mathematical or algorithmic methodology for the question\",\n", - " \"accepted_values\": [\"inappropriate\", \"barely adequate\", \"adequate\", \"mostly effective\", \"completely effective\"]\n", - " },\n", - " \"Calculation Correctness\": {\n", - " \"description\": \"Accuracy of calculations made and solutions given\",\n", - " \"accepted_values\": [\"completely incorrect\", \"mostly incorrect\", \"neither\", \"mostly correct\", \"completely correct\"]\n", - " },\n", - " \"Explanation Clarity\": {\n", - " \"description\": \"Clarity and comprehensibility of explanations, including language use and structure\",\n", - " \"accepted_values\": [\"not at all clear\", \"slightly clear\", \"moderately clear\", \"very clear\", \"completely clear\"]\n", - " },\n", - " \"Code Efficiency\": {\n", - " \"description\": \"Quality of code in terms of efficiency and elegance\",\n", - " \"accepted_values\": [\"not at all efficient\", \"slightly efficient\", \"moderately efficient\", \"very efficient\", \"extremely efficient\"]\n", - " },\n", - " \"Code Correctness\": {\n", - " \"description\": \"Correctness of the provided code\",\n", - " \"accepted_values\": [\"completely incorrect\", \"mostly incorrect\", \"partly correct\", \"mostly correct\", \"completely correct\"]\n", - " }\n", - " }\n", - "actual test case to evaluate: {\n", - " \"problem\": \"How many positive and negative integers is $12$ a multiple of?\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Prealgebra\",\n", - " \"solution\": \"The number $12$ is a multiple of $-12, -6, -4, -3, -2, -1, 1, 2, 3, 4, 6,$ and $12,$ for a total of $\\\\boxed{12}$ integers.\",\n", - " \"problem_id\": \"1\",\n", - " \"response_with_ans\": \"```python\\nnum = 12\\ndivisors = [i for i in range(1, num + 1) if num % i == 0]\\n\\npositive_divisors_count = len(divisors)\\nnegative_divisors_count = positive_divisors_count\\ntotal_divisors_count = positive_divisors_count + negative_divisors_count\\n\\nprint(f\\\"Positive divisors of 12: {divisors}\\\")\\ndivisors_negative = [-x for x in divisors]\\nprint(f\\\"Negative divisors of 12: {divisors_negative}\\\")\\nprint(f\\\"Total divisors count: {total_divisors_count}\\\")\\n```\\n\\nExecute this code snippet to find the divisors of 12 and count how many positive and negative integers 12 is a multiple of.\",\n", - " \"round\": 1,\n", - " \"messages\": [\n", - " {\n", - " \"content\": \"How many positive and negative integers is $12$ a multiple of?\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"To determine how many positive and negative integers 12 is a multiple of, we need to find the divisors of 12. Then we will double the number of divisors, as each positive divisor has a corresponding negative divisor.\\n\\nLet's use a Python code snippet to find all the divisors of 12 and count them.\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"```python\\nnum = 12\\ndivisors = [i for i in range(1, num + 1) if num % i == 0]\\n\\npositive_divisors_count = len(divisors)\\nnegative_divisors_count = positive_divisors_count\\ntotal_divisors_count = positive_divisors_count + negative_divisors_count\\n\\nprint(f\\\"Positive divisors of 12: {divisors}\\\")\\ndivisors_negative = [-x for x in divisors]\\nprint(f\\\"Negative divisors of 12: {divisors_negative}\\\")\\nprint(f\\\"Total divisors count: {total_divisors_count}\\\")\\n```\\n\\nExecute this code snippet to find the divisors of 12 and count how many positive and negative integers 12 is a multiple of.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 17.360238790512085,\n", - " \"trial\": -1\n", - "}\n", + " Task description: Given any question, the system needs to solve the problem as consisely and accurately as possible\n", + " Task successful example: {'problem': 'What is the sum of all the distinct positive two-digit factors of 144?', 'level': 'Level 5', 'type': 'Number Theory', 'solution': 'Prime factorize $144=2^4\\\\cdot3^2$. The sum of the positive two-digit factors of 144 is $2^4+2\\\\cdot3^2+2^2\\\\cdot3+2^2\\\\cdot3^2+2^3\\\\cdot3+2^3\\\\cdot3^2+2^4\\\\cdot3=\\\\boxed{226}.$', 'problem_id': '0', 'response_with_ans': 'To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere\\'s a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\"The sum of all the distinct positive two-digit factors of 144 is:\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.', 'round': 0, 'messages': [{'content': 'What is the sum of all the distinct positive two-digit factors of 144?', 'role': 'user'}, {'content': 'To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere\\'s a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\"The sum of all the distinct positive two-digit factors of 144 is:\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.', 'role': 'assistant'}], 'time': 11.140539407730103, 'trial': -1}\n", + " Task failed example: {'problem': 'Find all $x$ that satisfy the inequality $(2x+10)(x+3)<(3x+9)(x+8)$. Express your answer in interval notation.', 'level': 'Level 5', 'type': 'Algebra', 'solution': 'We have \\\\begin{align*} (2x+10)(x+3)&<(3x+9)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)&<3(x+3)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)-3(x+3)(x+8)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (2x+10-(3x+24))(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (-x-14)(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (x+14)(x+3)&>0.\\n\\\\end{align*} This inequality is satisfied if and only if $(x+14)$ and $(x+3)$ are either both positive or both negative. Both factors are positive for $x>-3$ and both factors are negative for $x<-14$. When $-14 0$ and then find intervals where the inequality holds true.\\n\\nLet's first find the factorized form of the quadratic:\", 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': \"I apologize for the confusion in my previous message. Let's find the factorized form of the quadratic using Python code.\\n\\n```python\\nfrom sympy import symbols, factor\\r\\nx = symbols('x')\\r\\nfactorized = factor(x**2 + 17*x + 42)\\r\\nfactorized\\r\\n```\\n\\nPlease run the Python code above to find the factorized form of the quadratic, and we can proceed to determine the intervals where the inequality holds true.\", 'role': 'assistant'}], 'time': 24.91333508491516, 'trial': -1}\n", + " Evaluation dictionary: [\n", + " {\n", + " \"name\": \"Problem Interpretation\",\n", + " \"description\": \"Ability to correctly interpret the problem.\",\n", + " \"accepted_values\": [\n", + " \"completely off\",\n", + " \"slightly relevant\",\n", + " \"relevant\",\n", + " \"mostly accurate\",\n", + " \"completely accurate\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Mathematical Methodology\",\n", + " \"description\": \"Adequacy of the chosen mathematical or algorithmic methodology for the question\",\n", + " \"accepted_values\": [\n", + " \"inappropriate\",\n", + " \"barely adequate\",\n", + " \"adequate\",\n", + " \"mostly effective\",\n", + " \"completely effective\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Calculation Correctness\",\n", + " \"description\": \"Accuracy of calculations made and solutions given\",\n", + " \"accepted_values\": [\n", + " \"completely incorrect\",\n", + " \"mostly incorrect\",\n", + " \"neither\",\n", + " \"mostly correct\",\n", + " \"completely correct\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Explanation Clarity\",\n", + " \"description\": \"Clarity and comprehensibility of explanations, including language use and structure\",\n", + " \"accepted_values\": [\n", + " \"not at all clear\",\n", + " \"slightly clear\",\n", + " \"moderately clear\",\n", + " \"very clear\",\n", + " \"completely clear\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Code Efficiency\",\n", + " \"description\": \"Quality of code in terms of efficiency and elegance\",\n", + " \"accepted_values\": [\n", + " \"not at all efficient\",\n", + " \"slightly efficient\",\n", + " \"moderately efficient\",\n", + " \"very efficient\",\n", + " \"extremely efficient\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Code Correctness\",\n", + " \"description\": \"Correctness of the provided code\",\n", + " \"accepted_values\": [\n", + " \"completely incorrect\",\n", + " \"mostly incorrect\",\n", + " \"partly correct\",\n", + " \"mostly correct\",\n", + " \"completely correct\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " }\n", + "]actual test case to evaluate: {'problem': 'How many integers $n$ satisfy both of the inequalities $4n + 3 < 25$ and $-7n + 5 < 24$?', 'level': 'Level 5', 'type': 'Prealgebra', 'solution': 'Subtract 3 and divide by 4 on both sides of the first inequality to obtain \\\\begin{align*}\\n4n + 3 &< 25 \\\\\\\\\\n\\\\Rightarrow\\\\qquad 4n &< 22 \\\\\\\\\\n\\\\Rightarrow\\\\qquad n &< 5.5.\\n\\\\end{align*}Similarly, the second inequality yields \\\\begin{align*}\\n-7n + 5 &< 24 \\\\\\\\\\n\\\\Rightarrow\\\\qquad -7n &< 19 \\\\\\\\\\n\\\\Rightarrow\\\\qquad n &> -\\\\frac{19}{7}.\\n\\\\end{align*}Therefore, we are looking for all the integers between $-\\\\frac{19}{7}$ and $5.5$. Since $-\\\\frac{19}{7}$ is between $-3$ and $-2$ and the largest integer less than $5.5$ is 5, we need to count the number of integers between $-2$ and $5$, inclusive. There are $5$ positive integers, $2$ negative integers, and zero, so there are $\\\\boxed{8}$ integers that satisfy both $4n + 3 < 25$ and $-7n + 5 < 24$.', 'problem_id': '10', 'response_with_ans': 'Here\\'s the code to solve the inequalities and find the intersection of their solutions:\\n\\n```python\\nfrom sympy import symbols, Eq, solve\\n\\nn = symbols(\"n\")\\ninequality_1 = 4 * n + 3 < 25\\ninequality_2 = -7 * n + 5 < 24\\n\\nsolution_1 = solve(inequality_1, n)\\nsolution_2 = solve(inequality_2, n)\\n\\nintersection = (max(solution_1[0], solution_2[0]), min(solution_1[1], solution_2[1]))\\n\\nprint(f\"Solution to inequality 1: {solution_1}\")\\nprint(f\"Solution to inequality 2: {solution_2}\")\\nprint(f\"Intersection of solutions: {intersection}\")\\n```\\n\\nExecute this code, and let\\'s see the solutions for both inequalities and their intersection.', 'round': 1, 'messages': [{'content': 'How many integers $n$ satisfy both of the inequalities $4n + 3 < 25$ and $-7n + 5 < 24$?', 'role': 'user'}, {'content': \"To find the number of integers $n$ that satisfy both inequalities, we'll first solve each inequality individually, and then find the intersection of the solutions.\\n\\nStep 1: Solve the inequalities\\n1. $4n + 3 < 25$\\n2. $-7n + 5 < 24$\\n\\nStep 2: Find the intersection of the solutions\\n\\nStep 3: Count the number of integers in the intersection\\n\\nFirst, let's solve the inequalities using the python code.\", 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': 'Here\\'s the code to solve the inequalities and find the intersection of their solutions:\\n\\n```python\\nfrom sympy import symbols, Eq, solve\\n\\nn = symbols(\"n\")\\ninequality_1 = 4 * n + 3 < 25\\ninequality_2 = -7 * n + 5 < 24\\n\\nsolution_1 = solve(inequality_1, n)\\nsolution_2 = solve(inequality_2, n)\\n\\nintersection = (max(solution_1[0], solution_2[0]), min(solution_1[1], solution_2[1]))\\n\\nprint(f\"Solution to inequality 1: {solution_1}\")\\nprint(f\"Solution to inequality 2: {solution_2}\")\\nprint(f\"Intersection of solutions: {intersection}\")\\n```\\n\\nExecute this code, and let\\'s see the solutions for both inequalities and their intersection.', 'role': 'assistant'}], 'time': 19.949471950531006, 'trial': -1}\n", "\n", "--------------------------------------------------------------------------------\n", + "\u001b[31m\n", + ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", "\u001b[33mquantifier\u001b[0m (to quantifier_user):\n", "\n", + "```json\n", "{\n", " \"Problem Interpretation\": \"completely accurate\",\n", " \"Mathematical Methodology\": \"completely effective\",\n", @@ -1136,494 +937,289 @@ " \"Code Efficiency\": \"moderately efficient\",\n", " \"Code Correctness\": \"completely correct\"\n", "}\n", + "```\n", "\n", "--------------------------------------------------------------------------------\n", - "actual label for this case: false\n", "\u001b[33mquantifier_user\u001b[0m (to quantifier):\n", "\n", "Task: Math problem solving.\n", - "Task description: Given any question, the system needs to solve the problem as consisely and accurately as possible\n", - "Task successful example: {\n", - " \"problem\": \"What is the sum of all the distinct positive two-digit factors of 144?\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Number Theory\",\n", - " \"solution\": \"Prime factorize $144=2^4\\\\cdot3^2$. The sum of the positive two-digit factors of 144 is $2^4+2\\\\cdot3^2+2^2\\\\cdot3+2^2\\\\cdot3^2+2^3\\\\cdot3+2^3\\\\cdot3^2+2^4\\\\cdot3=\\\\boxed{226}.$\",\n", - " \"problem_id\": \"0\",\n", - " \"response_with_ans\": \"To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere's a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\\\"The sum of all the distinct positive two-digit factors of 144 is:\\\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.\",\n", - " \"round\": 0,\n", - " \"messages\": [\n", - " {\n", - " \"content\": \"What is the sum of all the distinct positive two-digit factors of 144?\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere's a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\\\"The sum of all the distinct positive two-digit factors of 144 is:\\\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 11.140539407730103,\n", - " \"trial\": -1\n", - "}\n", - "\n", - "Task failed example: {\n", - " \"problem\": \"Find all $x$ that satisfy the inequality $(2x+10)(x+3)<(3x+9)(x+8)$. Express your answer in interval notation.\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Algebra\",\n", - " \"solution\": \"We have \\\\begin{align*} (2x+10)(x+3)&<(3x+9)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)&<3(x+3)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)-3(x+3)(x+8)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (2x+10-(3x+24))(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (-x-14)(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (x+14)(x+3)&>0.\\n\\\\end{align*} This inequality is satisfied if and only if $(x+14)$ and $(x+3)$ are either both positive or both negative. Both factors are positive for $x>-3$ and both factors are negative for $x<-14$. When $-14 0$ and then find intervals where the inequality holds true.\\n\\nLet's first find the factorized form of the quadratic:\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"I apologize for the confusion in my previous message. Let's find the factorized form of the quadratic using Python code.\\n\\n```python\\nfrom sympy import symbols, factor\\r\\nx = symbols('x')\\r\\nfactorized = factor(x**2 + 17*x + 42)\\r\\nfactorized\\r\\n```\\n\\nPlease run the Python code above to find the factorized form of the quadratic, and we can proceed to determine the intervals where the inequality holds true.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 24.91333508491516,\n", - " \"trial\": -1\n", - "}\n", - "\n", - "Evaluation dictionary: {\n", - " \"Problem Interpretation\": {\n", - " \"description\": \"Ability to correctly interpret the problem.\",\n", - " \"accepted_values\": [\"completely off\", \"slightly relevant\", \"relevant\", \"mostly accurate\", \"completely accurate\"]\n", - " },\n", - " \"Mathematical Methodology\": {\n", - " \"description\": \"Adequacy of the chosen mathematical or algorithmic methodology for the question\",\n", - " \"accepted_values\": [\"inappropriate\", \"barely adequate\", \"adequate\", \"mostly effective\", \"completely effective\"]\n", - " },\n", - " \"Calculation Correctness\": {\n", - " \"description\": \"Accuracy of calculations made and solutions given\",\n", - " \"accepted_values\": [\"completely incorrect\", \"mostly incorrect\", \"neither\", \"mostly correct\", \"completely correct\"]\n", - " },\n", - " \"Explanation Clarity\": {\n", - " \"description\": \"Clarity and comprehensibility of explanations, including language use and structure\",\n", - " \"accepted_values\": [\"not at all clear\", \"slightly clear\", \"moderately clear\", \"very clear\", \"completely clear\"]\n", - " },\n", - " \"Code Efficiency\": {\n", - " \"description\": \"Quality of code in terms of efficiency and elegance\",\n", - " \"accepted_values\": [\"not at all efficient\", \"slightly efficient\", \"moderately efficient\", \"very efficient\", \"extremely efficient\"]\n", - " },\n", - " \"Code Correctness\": {\n", - " \"description\": \"Correctness of the provided code\",\n", - " \"accepted_values\": [\"completely incorrect\", \"mostly incorrect\", \"partly correct\", \"mostly correct\", \"completely correct\"]\n", - " }\n", + " Task description: Given any question, the system needs to solve the problem as consisely and accurately as possible\n", + " Task successful example: {'problem': 'What is the sum of all the distinct positive two-digit factors of 144?', 'level': 'Level 5', 'type': 'Number Theory', 'solution': 'Prime factorize $144=2^4\\\\cdot3^2$. The sum of the positive two-digit factors of 144 is $2^4+2\\\\cdot3^2+2^2\\\\cdot3+2^2\\\\cdot3^2+2^3\\\\cdot3+2^3\\\\cdot3^2+2^4\\\\cdot3=\\\\boxed{226}.$', 'problem_id': '0', 'response_with_ans': 'To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere\\'s a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\"The sum of all the distinct positive two-digit factors of 144 is:\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.', 'round': 0, 'messages': [{'content': 'What is the sum of all the distinct positive two-digit factors of 144?', 'role': 'user'}, {'content': 'To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere\\'s a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\"The sum of all the distinct positive two-digit factors of 144 is:\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.', 'role': 'assistant'}], 'time': 11.140539407730103, 'trial': -1}\n", + " Task failed example: {'problem': 'Find all $x$ that satisfy the inequality $(2x+10)(x+3)<(3x+9)(x+8)$. Express your answer in interval notation.', 'level': 'Level 5', 'type': 'Algebra', 'solution': 'We have \\\\begin{align*} (2x+10)(x+3)&<(3x+9)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)&<3(x+3)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)-3(x+3)(x+8)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (2x+10-(3x+24))(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (-x-14)(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (x+14)(x+3)&>0.\\n\\\\end{align*} This inequality is satisfied if and only if $(x+14)$ and $(x+3)$ are either both positive or both negative. Both factors are positive for $x>-3$ and both factors are negative for $x<-14$. When $-14 0$ and then find intervals where the inequality holds true.\\n\\nLet's first find the factorized form of the quadratic:\", 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': \"I apologize for the confusion in my previous message. Let's find the factorized form of the quadratic using Python code.\\n\\n```python\\nfrom sympy import symbols, factor\\r\\nx = symbols('x')\\r\\nfactorized = factor(x**2 + 17*x + 42)\\r\\nfactorized\\r\\n```\\n\\nPlease run the Python code above to find the factorized form of the quadratic, and we can proceed to determine the intervals where the inequality holds true.\", 'role': 'assistant'}], 'time': 24.91333508491516, 'trial': -1}\n", + " Evaluation dictionary: [\n", + " {\n", + " \"name\": \"Problem Interpretation\",\n", + " \"description\": \"Ability to correctly interpret the problem.\",\n", + " \"accepted_values\": [\n", + " \"completely off\",\n", + " \"slightly relevant\",\n", + " \"relevant\",\n", + " \"mostly accurate\",\n", + " \"completely accurate\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Mathematical Methodology\",\n", + " \"description\": \"Adequacy of the chosen mathematical or algorithmic methodology for the question\",\n", + " \"accepted_values\": [\n", + " \"inappropriate\",\n", + " \"barely adequate\",\n", + " \"adequate\",\n", + " \"mostly effective\",\n", + " \"completely effective\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Calculation Correctness\",\n", + " \"description\": \"Accuracy of calculations made and solutions given\",\n", + " \"accepted_values\": [\n", + " \"completely incorrect\",\n", + " \"mostly incorrect\",\n", + " \"neither\",\n", + " \"mostly correct\",\n", + " \"completely correct\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Explanation Clarity\",\n", + " \"description\": \"Clarity and comprehensibility of explanations, including language use and structure\",\n", + " \"accepted_values\": [\n", + " \"not at all clear\",\n", + " \"slightly clear\",\n", + " \"moderately clear\",\n", + " \"very clear\",\n", + " \"completely clear\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Code Efficiency\",\n", + " \"description\": \"Quality of code in terms of efficiency and elegance\",\n", + " \"accepted_values\": [\n", + " \"not at all efficient\",\n", + " \"slightly efficient\",\n", + " \"moderately efficient\",\n", + " \"very efficient\",\n", + " \"extremely efficient\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Code Correctness\",\n", + " \"description\": \"Correctness of the provided code\",\n", + " \"accepted_values\": [\n", + " \"completely incorrect\",\n", + " \"mostly incorrect\",\n", + " \"partly correct\",\n", + " \"mostly correct\",\n", + " \"completely correct\"\n", + " ],\n", + " \"sub_criteria\": []\n", " }\n", - "actual test case to evaluate: {\n", - " \"problem\": \"In isosceles right triangle $ABC$, point $D$ is on hypotenuse $\\\\overline{BC}$ such that $\\\\overline{AD}$ is an altitude of $\\\\triangle ABC$ and $DC = 5$. What is the area of triangle $ABC$?\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Prealgebra\",\n", - " \"solution\": \"In isosceles right triangle $\\\\triangle ABC$ below, $\\\\overline{AD}$ is the altitude to the hypotenuse.\\n\\n[asy]\\nimport olympiad;\\nunitsize(0.8inch);\\npair A,B,C,D;\\nA = (0,1);\\nB= (1,0);\\nC = -B;\\nD = (0,0);\\ndraw(A--B--C--A,linewidth(1));\\ndraw(A--D,linewidth(0.8));\\ndraw(rightanglemark(C,A,B,s=4));\\ndraw(rightanglemark(C,D,A,s=4));\\nlabel(\\\"$A$\\\",A,N);\\nlabel(\\\"$B$\\\",B,S);\\nlabel(\\\"$C$\\\",C,S);\\nlabel(\\\"$D$\\\",D,S);\\n[/asy]\\n\\nBecause $\\\\triangle ABC$ is an isosceles right triangle, $\\\\angle ABC = 45^\\\\circ$. Since $\\\\angle ADB = 90^\\\\circ$, we know that $\\\\angle DAB = 45^\\\\circ$, so $\\\\triangle ABD$ is also a 45-45-90 triangle. Similarly, $\\\\triangle ACD$ is a 45-45-90 triangle. Therefore, $DA=DB = DC = 5$, so $BC = BD+DC = 10$, and \\\\[[ABC] = \\\\frac{(AD)(BC)}{2} = \\\\frac{(5)({10})}{2} = \\\\boxed{25}.\\\\]\",\n", - " \"problem_id\": \"13\",\n", - " \"response_with_ans\": \"```python\\nfrom sympy import Eq, solve, symbols\\r\\n\\r\\n# Step 1: Find the length of side AC using the Pythagorean theorem\\r\\na, b, c = symbols(\\\"a b c\\\")\\r\\nPythagorean = Eq(a**2 + b**2, c**2)\\r\\n\\r\\n# We know BD = 5 and it's an isosceles right triangle so:\\r\\nBD = 5\\r\\nBC = BD * 2\\r\\nside_A = BD # Side A is equal to BD\\r\\n\\r\\nisosceles_triangle = Pythagorean.subs({a: side_A, b: side_A, c: BC}) # Substitute values into the equation\\r\\nlength_AC = solve(isosceles_triangle, side_A)[0] # Solve for side_AC\\r\\n\\r\\n# Step 2: Find the length of side AD\\r\\nlength_AD = (BD * length_AC) / BC # Using the proportion\\r\\n\\r\\n# Step 3: Find the area of triangle ABC\\r\\narea_triangle_ABC = 0.5 * (length_AD * length_AC)\\r\\n\\r\\nlength_AC, length_AD, area_triangle_ABC\\r\\n```\",\n", - " \"round\": 1,\n", - " \"messages\": [\n", - " {\n", - " \"content\": \"In isosceles right triangle $ABC$, point $D$ is on hypotenuse $\\\\overline{BC}$ such that $\\\\overline{AD}$ is an altitude of $\\\\triangle ABC$ and $DC = 5$. What is the area of triangle $ABC$?\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"Let's first analyze the problem and draw a plan to solve it.\\n\\nPlan:\\n1. Use the Pythagorean theorem to find the length of side AC.\\n2. Since right triangle ADC is similar to triangle ABC, we can set up a proportion to find the length of side AD.\\n3. Use the area formula of a triangle to find the area of triangle ABC.\\n\\nNow, let's execute the plan using code to calculate side AC, then side AD, and finally the area of triangle ABC.\\n\\n\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"```python\\nfrom sympy import Eq, solve, symbols\\r\\n\\r\\n# Step 1: Find the length of side AC using the Pythagorean theorem\\r\\na, b, c = symbols(\\\"a b c\\\")\\r\\nPythagorean = Eq(a**2 + b**2, c**2)\\r\\n\\r\\n# We know BD = 5 and it's an isosceles right triangle so:\\r\\nBD = 5\\r\\nBC = BD * 2\\r\\nside_A = BD # Side A is equal to BD\\r\\n\\r\\nisosceles_triangle = Pythagorean.subs({a: side_A, b: side_A, c: BC}) # Substitute values into the equation\\r\\nlength_AC = solve(isosceles_triangle, side_A)[0] # Solve for side_AC\\r\\n\\r\\n# Step 2: Find the length of side AD\\r\\nlength_AD = (BD * length_AC) / BC # Using the proportion\\r\\n\\r\\n# Step 3: Find the area of triangle ABC\\r\\narea_triangle_ABC = 0.5 * (length_AD * length_AC)\\r\\n\\r\\nlength_AC, length_AD, area_triangle_ABC\\r\\n```\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 22.85700249671936,\n", - " \"trial\": -1\n", - "}\n", + "]actual test case to evaluate: {'problem': 'What is the sum of the lengths, in centimeters, of the two legs of a 30-60-90 right triangle, if the length of the hypotenuse is $2\\\\sqrt{6}$ centimeters?', 'level': 'Level 5', 'type': 'Prealgebra', 'solution': 'We know that the ratio of the lengths of the sides of a 30-60-90 triangle is $1:\\\\sqrt{3}:2$. We know that the length of the hypotenuse is $2\\\\sqrt{6}$ and the ratio of the length shortest leg to that of the hypotenuse is $1:2$. Therefore, the length of the shorter leg is $\\\\sqrt{6}$. Since the ratio of the shorter leg to the longer leg is $1:\\\\sqrt{3}$, the length of the longer leg is $\\\\sqrt{6} \\\\cdot \\\\sqrt{3} = 3\\\\sqrt{2}$. The sum of the lengths of these two legs is $\\\\boxed{\\\\sqrt{6} + 3\\\\sqrt{2}}$ centimeters.', 'problem_id': '17', 'response_with_ans': \"Let's solve for x and find the lengths of the legs.\\n\\n```python\\nimport math\\n\\nhypotenuse = 2 * math.sqrt(6)\\nx = hypotenuse / 2\\n\\nleg_1 = x\\nleg_2 = x * math.sqrt(3)\\nsum_of_legs = leg_1 + leg_2\\n\\nleg_1, leg_2, sum_of_legs\\n```\\n\\nNow, execute the above code to find the sum of the lengths of the two legs.\", 'round': 1, 'messages': [{'content': 'What is the sum of the lengths, in centimeters, of the two legs of a 30-60-90 right triangle, if the length of the hypotenuse is $2\\\\sqrt{6}$ centimeters?', 'role': 'user'}, {'content': \"To find the sum of the lengths of the two legs, let's first analyze a 30-60-90 right triangle. In a 30-60-90 triangle, the ratios of the side lengths are:\\n\\n1. Opposite the 30-degree angle: $x$\\n2. Opposite the 60-degree angle: $x\\\\sqrt{3}$\\n3. Hypotenuse (opposite the 90-degree angle): $2x$\\n\\nNow, we are given that the hypotenuse is $2\\\\sqrt{6}$ centimeters. We can set up the following equation and solve for $x$:\\n\\n$2x = 2\\\\sqrt{6}$\\n\\nNext, we will find the lengths of the legs and sum them up.\", 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': \"Let's solve for x and find the lengths of the legs.\\n\\n```python\\nimport math\\n\\nhypotenuse = 2 * math.sqrt(6)\\nx = hypotenuse / 2\\n\\nleg_1 = x\\nleg_2 = x * math.sqrt(3)\\nsum_of_legs = leg_1 + leg_2\\n\\nleg_1, leg_2, sum_of_legs\\n```\\n\\nNow, execute the above code to find the sum of the lengths of the two legs.\", 'role': 'assistant'}], 'time': 18.742590188980103, 'trial': -1}\n", "\n", "--------------------------------------------------------------------------------\n", + "\u001b[31m\n", + ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", "\u001b[33mquantifier\u001b[0m (to quantifier_user):\n", "\n", + "```json\n", "{\n", " \"Problem Interpretation\": \"completely accurate\",\n", " \"Mathematical Methodology\": \"completely effective\",\n", " \"Calculation Correctness\": \"completely correct\",\n", " \"Explanation Clarity\": \"very clear\",\n", - " \"Code Efficiency\": \"moderately efficient\",\n", - " \"Code Correctness\": \"mostly correct\"\n", + " \"Code Efficiency\": \"very efficient\",\n", + " \"Code Correctness\": \"completely correct\"\n", "}\n", + "```\n", "\n", "--------------------------------------------------------------------------------\n", - "actual label for this case: false\n", "\u001b[33mquantifier_user\u001b[0m (to quantifier):\n", "\n", "Task: Math problem solving.\n", - "Task description: Given any question, the system needs to solve the problem as consisely and accurately as possible\n", - "Task successful example: {\n", - " \"problem\": \"What is the sum of all the distinct positive two-digit factors of 144?\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Number Theory\",\n", - " \"solution\": \"Prime factorize $144=2^4\\\\cdot3^2$. The sum of the positive two-digit factors of 144 is $2^4+2\\\\cdot3^2+2^2\\\\cdot3+2^2\\\\cdot3^2+2^3\\\\cdot3+2^3\\\\cdot3^2+2^4\\\\cdot3=\\\\boxed{226}.$\",\n", - " \"problem_id\": \"0\",\n", - " \"response_with_ans\": \"To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere's a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\\\"The sum of all the distinct positive two-digit factors of 144 is:\\\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.\",\n", - " \"round\": 0,\n", - " \"messages\": [\n", - " {\n", - " \"content\": \"What is the sum of all the distinct positive two-digit factors of 144?\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere's a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\\\"The sum of all the distinct positive two-digit factors of 144 is:\\\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 11.140539407730103,\n", - " \"trial\": -1\n", - "}\n", - "\n", - "Task failed example: {\n", - " \"problem\": \"Find all $x$ that satisfy the inequality $(2x+10)(x+3)<(3x+9)(x+8)$. Express your answer in interval notation.\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Algebra\",\n", - " \"solution\": \"We have \\\\begin{align*} (2x+10)(x+3)&<(3x+9)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)&<3(x+3)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)-3(x+3)(x+8)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (2x+10-(3x+24))(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (-x-14)(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (x+14)(x+3)&>0.\\n\\\\end{align*} This inequality is satisfied if and only if $(x+14)$ and $(x+3)$ are either both positive or both negative. Both factors are positive for $x>-3$ and both factors are negative for $x<-14$. When $-14 0$ and then find intervals where the inequality holds true.\\n\\nLet's first find the factorized form of the quadratic:\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"I apologize for the confusion in my previous message. Let's find the factorized form of the quadratic using Python code.\\n\\n```python\\nfrom sympy import symbols, factor\\r\\nx = symbols('x')\\r\\nfactorized = factor(x**2 + 17*x + 42)\\r\\nfactorized\\r\\n```\\n\\nPlease run the Python code above to find the factorized form of the quadratic, and we can proceed to determine the intervals where the inequality holds true.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 24.91333508491516,\n", - " \"trial\": -1\n", - "}\n", - "\n", - "Evaluation dictionary: {\n", - " \"Problem Interpretation\": {\n", - " \"description\": \"Ability to correctly interpret the problem.\",\n", - " \"accepted_values\": [\"completely off\", \"slightly relevant\", \"relevant\", \"mostly accurate\", \"completely accurate\"]\n", - " },\n", - " \"Mathematical Methodology\": {\n", - " \"description\": \"Adequacy of the chosen mathematical or algorithmic methodology for the question\",\n", - " \"accepted_values\": [\"inappropriate\", \"barely adequate\", \"adequate\", \"mostly effective\", \"completely effective\"]\n", - " },\n", - " \"Calculation Correctness\": {\n", - " \"description\": \"Accuracy of calculations made and solutions given\",\n", - " \"accepted_values\": [\"completely incorrect\", \"mostly incorrect\", \"neither\", \"mostly correct\", \"completely correct\"]\n", - " },\n", - " \"Explanation Clarity\": {\n", - " \"description\": \"Clarity and comprehensibility of explanations, including language use and structure\",\n", - " \"accepted_values\": [\"not at all clear\", \"slightly clear\", \"moderately clear\", \"very clear\", \"completely clear\"]\n", - " },\n", - " \"Code Efficiency\": {\n", - " \"description\": \"Quality of code in terms of efficiency and elegance\",\n", - " \"accepted_values\": [\"not at all efficient\", \"slightly efficient\", \"moderately efficient\", \"very efficient\", \"extremely efficient\"]\n", - " },\n", - " \"Code Correctness\": {\n", - " \"description\": \"Correctness of the provided code\",\n", - " \"accepted_values\": [\"completely incorrect\", \"mostly incorrect\", \"partly correct\", \"mostly correct\", \"completely correct\"]\n", - " }\n", + " Task description: Given any question, the system needs to solve the problem as consisely and accurately as possible\n", + " Task successful example: {'problem': 'What is the sum of all the distinct positive two-digit factors of 144?', 'level': 'Level 5', 'type': 'Number Theory', 'solution': 'Prime factorize $144=2^4\\\\cdot3^2$. The sum of the positive two-digit factors of 144 is $2^4+2\\\\cdot3^2+2^2\\\\cdot3+2^2\\\\cdot3^2+2^3\\\\cdot3+2^3\\\\cdot3^2+2^4\\\\cdot3=\\\\boxed{226}.$', 'problem_id': '0', 'response_with_ans': 'To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere\\'s a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\"The sum of all the distinct positive two-digit factors of 144 is:\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.', 'round': 0, 'messages': [{'content': 'What is the sum of all the distinct positive two-digit factors of 144?', 'role': 'user'}, {'content': 'To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere\\'s a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\"The sum of all the distinct positive two-digit factors of 144 is:\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.', 'role': 'assistant'}], 'time': 11.140539407730103, 'trial': -1}\n", + " Task failed example: {'problem': 'Find all $x$ that satisfy the inequality $(2x+10)(x+3)<(3x+9)(x+8)$. Express your answer in interval notation.', 'level': 'Level 5', 'type': 'Algebra', 'solution': 'We have \\\\begin{align*} (2x+10)(x+3)&<(3x+9)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)&<3(x+3)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)-3(x+3)(x+8)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (2x+10-(3x+24))(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (-x-14)(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (x+14)(x+3)&>0.\\n\\\\end{align*} This inequality is satisfied if and only if $(x+14)$ and $(x+3)$ are either both positive or both negative. Both factors are positive for $x>-3$ and both factors are negative for $x<-14$. When $-14 0$ and then find intervals where the inequality holds true.\\n\\nLet's first find the factorized form of the quadratic:\", 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': \"I apologize for the confusion in my previous message. Let's find the factorized form of the quadratic using Python code.\\n\\n```python\\nfrom sympy import symbols, factor\\r\\nx = symbols('x')\\r\\nfactorized = factor(x**2 + 17*x + 42)\\r\\nfactorized\\r\\n```\\n\\nPlease run the Python code above to find the factorized form of the quadratic, and we can proceed to determine the intervals where the inequality holds true.\", 'role': 'assistant'}], 'time': 24.91333508491516, 'trial': -1}\n", + " Evaluation dictionary: [\n", + " {\n", + " \"name\": \"Problem Interpretation\",\n", + " \"description\": \"Ability to correctly interpret the problem.\",\n", + " \"accepted_values\": [\n", + " \"completely off\",\n", + " \"slightly relevant\",\n", + " \"relevant\",\n", + " \"mostly accurate\",\n", + " \"completely accurate\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Mathematical Methodology\",\n", + " \"description\": \"Adequacy of the chosen mathematical or algorithmic methodology for the question\",\n", + " \"accepted_values\": [\n", + " \"inappropriate\",\n", + " \"barely adequate\",\n", + " \"adequate\",\n", + " \"mostly effective\",\n", + " \"completely effective\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Calculation Correctness\",\n", + " \"description\": \"Accuracy of calculations made and solutions given\",\n", + " \"accepted_values\": [\n", + " \"completely incorrect\",\n", + " \"mostly incorrect\",\n", + " \"neither\",\n", + " \"mostly correct\",\n", + " \"completely correct\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Explanation Clarity\",\n", + " \"description\": \"Clarity and comprehensibility of explanations, including language use and structure\",\n", + " \"accepted_values\": [\n", + " \"not at all clear\",\n", + " \"slightly clear\",\n", + " \"moderately clear\",\n", + " \"very clear\",\n", + " \"completely clear\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Code Efficiency\",\n", + " \"description\": \"Quality of code in terms of efficiency and elegance\",\n", + " \"accepted_values\": [\n", + " \"not at all efficient\",\n", + " \"slightly efficient\",\n", + " \"moderately efficient\",\n", + " \"very efficient\",\n", + " \"extremely efficient\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Code Correctness\",\n", + " \"description\": \"Correctness of the provided code\",\n", + " \"accepted_values\": [\n", + " \"completely incorrect\",\n", + " \"mostly incorrect\",\n", + " \"partly correct\",\n", + " \"mostly correct\",\n", + " \"completely correct\"\n", + " ],\n", + " \"sub_criteria\": []\n", " }\n", - "actual test case to evaluate: {\n", - " \"problem\": \"John counts up from 1 to 13, and then immediately counts down again to 1, and then back up to 13, and so on, alternately counting up and down: \\\\begin{align*}\\n&(1, 2, 3,4,5,6,7,8,9,10,11,12,13,\\\\\\\\\\n&\\\\qquad\\\\qquad12,11,10,9,8,7,6,5,4,3,2,1,2,3,4,\\\\ldots ).\\n\\\\end{align*} What is the $5000^{\\\\text{th}}$ integer in his list?\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Prealgebra\",\n", - " \"solution\": \"We can treat this list as a sequence with a repetitive pattern. We see the sequence repeats itself every 24 elements (from 1 to 13 then back to 2). When 5000 is divided by 24, its remainder is 8. Therefore we see the $5000^{\\\\text{th}}$ integer is the same as the $8^{\\\\text{th}}$ integer, which is $\\\\boxed{8}$.\",\n", - " \"problem_id\": \"4\",\n", - " \"response_with_ans\": \"We can solve this problem by finding which \\\"group\\\" the 5000th number belongs to, and then determine the number within that group. Each group is either counting up from 1 to 13 or counting down from 13 to 1. There are 13 numbers in each direction, but since 1 is counted twice (once at the end of a down-count and once at the beginning of an up-count), counting up and down would consist of 25 numbers (13 up + 12 down).\\n\\nLet's first find out which group the 5000th number belongs to and how many numbers in total are in the complete up and down groups before 5000th number using code:\\n\\n```python\\nnum_group = 5000 // 25\\nremainder = 5000 % 25\\ntotal_numbers_before = num_group * 25\\nprint('Number of complete groups:', num_group)\\nprint('Remainder:', remainder)\\nprint('Numbers before 5000th number:', total_numbers_before)\\n```\\n\\nAfter knowing these values, I'll determine if the 5000th number is in up-count or down-count and its value.\\n\",\n", - " \"round\": 0,\n", - " \"messages\": [\n", - " {\n", - " \"content\": \"John counts up from 1 to 13, and then immediately counts down again to 1, and then back up to 13, and so on, alternately counting up and down: \\\\begin{align*}\\n&(1, 2, 3,4,5,6,7,8,9,10,11,12,13,\\\\\\\\\\n&\\\\qquad\\\\qquad12,11,10,9,8,7,6,5,4,3,2,1,2,3,4,\\\\ldots ).\\n\\\\end{align*} What is the $5000^{\\\\text{th}}$ integer in his list?\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"We can solve this problem by finding which \\\"group\\\" the 5000th number belongs to, and then determine the number within that group. Each group is either counting up from 1 to 13 or counting down from 13 to 1. There are 13 numbers in each direction, but since 1 is counted twice (once at the end of a down-count and once at the beginning of an up-count), counting up and down would consist of 25 numbers (13 up + 12 down).\\n\\nLet's first find out which group the 5000th number belongs to and how many numbers in total are in the complete up and down groups before 5000th number using code:\\n\\n```python\\nnum_group = 5000 // 25\\nremainder = 5000 % 25\\ntotal_numbers_before = num_group * 25\\nprint('Number of complete groups:', num_group)\\nprint('Remainder:', remainder)\\nprint('Numbers before 5000th number:', total_numbers_before)\\n```\\n\\nAfter knowing these values, I'll determine if the 5000th number is in up-count or down-count and its value.\\n\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 16.342331409454346,\n", - " \"trial\": -1\n", - "}\n", + "]actual test case to evaluate: {'problem': 'How many positive and negative integers is $12$ a multiple of?', 'level': 'Level 5', 'type': 'Prealgebra', 'solution': 'The number $12$ is a multiple of $-12, -6, -4, -3, -2, -1, 1, 2, 3, 4, 6,$ and $12,$ for a total of $\\\\boxed{12}$ integers.', 'problem_id': '1', 'response_with_ans': '```python\\nnum = 12\\ndivisors = [i for i in range(1, num + 1) if num % i == 0]\\n\\npositive_divisors_count = len(divisors)\\nnegative_divisors_count = positive_divisors_count\\ntotal_divisors_count = positive_divisors_count + negative_divisors_count\\n\\nprint(f\"Positive divisors of 12: {divisors}\")\\ndivisors_negative = [-x for x in divisors]\\nprint(f\"Negative divisors of 12: {divisors_negative}\")\\nprint(f\"Total divisors count: {total_divisors_count}\")\\n```\\n\\nExecute this code snippet to find the divisors of 12 and count how many positive and negative integers 12 is a multiple of.', 'round': 1, 'messages': [{'content': 'How many positive and negative integers is $12$ a multiple of?', 'role': 'user'}, {'content': \"To determine how many positive and negative integers 12 is a multiple of, we need to find the divisors of 12. Then we will double the number of divisors, as each positive divisor has a corresponding negative divisor.\\n\\nLet's use a Python code snippet to find all the divisors of 12 and count them.\", 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': '```python\\nnum = 12\\ndivisors = [i for i in range(1, num + 1) if num % i == 0]\\n\\npositive_divisors_count = len(divisors)\\nnegative_divisors_count = positive_divisors_count\\ntotal_divisors_count = positive_divisors_count + negative_divisors_count\\n\\nprint(f\"Positive divisors of 12: {divisors}\")\\ndivisors_negative = [-x for x in divisors]\\nprint(f\"Negative divisors of 12: {divisors_negative}\")\\nprint(f\"Total divisors count: {total_divisors_count}\")\\n```\\n\\nExecute this code snippet to find the divisors of 12 and count how many positive and negative integers 12 is a multiple of.', 'role': 'assistant'}], 'time': 17.360238790512085, 'trial': -1}\n", "\n", "--------------------------------------------------------------------------------\n", + "\u001b[31m\n", + ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", "\u001b[33mquantifier\u001b[0m (to quantifier_user):\n", "\n", "```json\n", "{\n", " \"Problem Interpretation\": \"completely accurate\",\n", - " \"Mathematical Methodology\": \"mostly effective\",\n", - " \"Calculation Correctness\": \"mostly correct\",\n", + " \"Mathematical Methodology\": \"completely effective\",\n", + " \"Calculation Correctness\": \"completely correct\",\n", " \"Explanation Clarity\": \"very clear\",\n", " \"Code Efficiency\": \"moderately efficient\",\n", - " \"Code Correctness\": \"mostly correct\"\n", + " \"Code Correctness\": \"completely correct\"\n", "}\n", "```\n", "\n", "--------------------------------------------------------------------------------\n", - "actual label for this case: false\n", - "\u001b[33mquantifier_user\u001b[0m (to quantifier):\n", - "\n", - "Task: Math problem solving.\n", - "Task description: Given any question, the system needs to solve the problem as consisely and accurately as possible\n", - "Task successful example: {\n", - " \"problem\": \"What is the sum of all the distinct positive two-digit factors of 144?\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Number Theory\",\n", - " \"solution\": \"Prime factorize $144=2^4\\\\cdot3^2$. The sum of the positive two-digit factors of 144 is $2^4+2\\\\cdot3^2+2^2\\\\cdot3+2^2\\\\cdot3^2+2^3\\\\cdot3+2^3\\\\cdot3^2+2^4\\\\cdot3=\\\\boxed{226}.$\",\n", - " \"problem_id\": \"0\",\n", - " \"response_with_ans\": \"To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere's a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\\\"The sum of all the distinct positive two-digit factors of 144 is:\\\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.\",\n", - " \"round\": 0,\n", - " \"messages\": [\n", - " {\n", - " \"content\": \"What is the sum of all the distinct positive two-digit factors of 144?\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere's a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\\\"The sum of all the distinct positive two-digit factors of 144 is:\\\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 11.140539407730103,\n", - " \"trial\": -1\n", - "}\n", - "\n", - "Task failed example: {\n", - " \"problem\": \"Find all $x$ that satisfy the inequality $(2x+10)(x+3)<(3x+9)(x+8)$. Express your answer in interval notation.\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Algebra\",\n", - " \"solution\": \"We have \\\\begin{align*} (2x+10)(x+3)&<(3x+9)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)&<3(x+3)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)-3(x+3)(x+8)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (2x+10-(3x+24))(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (-x-14)(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (x+14)(x+3)&>0.\\n\\\\end{align*} This inequality is satisfied if and only if $(x+14)$ and $(x+3)$ are either both positive or both negative. Both factors are positive for $x>-3$ and both factors are negative for $x<-14$. When $-14 0$ and then find intervals where the inequality holds true.\\n\\nLet's first find the factorized form of the quadratic:\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"I apologize for the confusion in my previous message. Let's find the factorized form of the quadratic using Python code.\\n\\n```python\\nfrom sympy import symbols, factor\\r\\nx = symbols('x')\\r\\nfactorized = factor(x**2 + 17*x + 42)\\r\\nfactorized\\r\\n```\\n\\nPlease run the Python code above to find the factorized form of the quadratic, and we can proceed to determine the intervals where the inequality holds true.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 24.91333508491516,\n", - " \"trial\": -1\n", - "}\n", - "\n", - "Evaluation dictionary: {\n", - " \"Problem Interpretation\": {\n", - " \"description\": \"Ability to correctly interpret the problem.\",\n", - " \"accepted_values\": [\"completely off\", \"slightly relevant\", \"relevant\", \"mostly accurate\", \"completely accurate\"]\n", - " },\n", - " \"Mathematical Methodology\": {\n", - " \"description\": \"Adequacy of the chosen mathematical or algorithmic methodology for the question\",\n", - " \"accepted_values\": [\"inappropriate\", \"barely adequate\", \"adequate\", \"mostly effective\", \"completely effective\"]\n", - " },\n", - " \"Calculation Correctness\": {\n", - " \"description\": \"Accuracy of calculations made and solutions given\",\n", - " \"accepted_values\": [\"completely incorrect\", \"mostly incorrect\", \"neither\", \"mostly correct\", \"completely correct\"]\n", - " },\n", - " \"Explanation Clarity\": {\n", - " \"description\": \"Clarity and comprehensibility of explanations, including language use and structure\",\n", - " \"accepted_values\": [\"not at all clear\", \"slightly clear\", \"moderately clear\", \"very clear\", \"completely clear\"]\n", - " },\n", - " \"Code Efficiency\": {\n", - " \"description\": \"Quality of code in terms of efficiency and elegance\",\n", - " \"accepted_values\": [\"not at all efficient\", \"slightly efficient\", \"moderately efficient\", \"very efficient\", \"extremely efficient\"]\n", - " },\n", - " \"Code Correctness\": {\n", - " \"description\": \"Correctness of the provided code\",\n", - " \"accepted_values\": [\"completely incorrect\", \"mostly incorrect\", \"partly correct\", \"mostly correct\", \"completely correct\"]\n", - " }\n", - " }\n", - "actual test case to evaluate: {\n", - " \"problem\": \"An equilateral triangle has a side of length 12 inches. What is the area of the triangle, in square inches? Express your answer in simplest radical form.\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Prealgebra\",\n", - " \"solution\": \"The area of an equilateral triangle with side length $s$ is $s^2\\\\sqrt{3}/4$. We have $s = 12$, so our area is $12^2\\\\sqrt{3}/4 = \\\\boxed{36\\\\sqrt{3}}$.\",\n", - " \"problem_id\": \"18\",\n", - " \"response_with_ans\": \"```python\\nimport math\\n\\nside_length = 12\\narea = (math.sqrt(3) * side_length**2) / 4\\nprint(area)\\n```\",\n", - " \"round\": 1,\n", - " \"messages\": [\n", - " {\n", - " \"content\": \"An equilateral triangle has a side of length 12 inches. What is the area of the triangle, in square inches? Express your answer in simplest radical form.\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"To find the area of an equilateral triangle with a side of length s, we can use the formula:\\n\\narea = (sqrt(3) * s\\u00b2) / 4\\n\\nIn this case, s = 12 inches. Let's calculate the area.\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"```python\\nimport math\\n\\nside_length = 12\\narea = (math.sqrt(3) * side_length**2) / 4\\nprint(area)\\n```\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 14.153439283370972,\n", - " \"trial\": -1\n", - "}\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mquantifier\u001b[0m (to quantifier_user):\n", - "\n", - "{\n", - " \"Problem Interpretation\": \"completely accurate\",\n", - " \"Mathematical Methodology\": \"completely effective\",\n", - " \"Calculation Correctness\": \"completely correct\",\n", - " \"Explanation Clarity\": \"very clear\",\n", - " \"Code Efficiency\": \"very efficient\",\n", - " \"Code Correctness\": \"completely correct\"\n", - "}\n", - "\n", - "--------------------------------------------------------------------------------\n", - "actual label for this case: false\n", "\u001b[33mquantifier_user\u001b[0m (to quantifier):\n", "\n", "Task: Math problem solving.\n", - "Task description: Given any question, the system needs to solve the problem as consisely and accurately as possible\n", - "Task successful example: {\n", - " \"problem\": \"What is the sum of all the distinct positive two-digit factors of 144?\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Number Theory\",\n", - " \"solution\": \"Prime factorize $144=2^4\\\\cdot3^2$. The sum of the positive two-digit factors of 144 is $2^4+2\\\\cdot3^2+2^2\\\\cdot3+2^2\\\\cdot3^2+2^3\\\\cdot3+2^3\\\\cdot3^2+2^4\\\\cdot3=\\\\boxed{226}.$\",\n", - " \"problem_id\": \"0\",\n", - " \"response_with_ans\": \"To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere's a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\\\"The sum of all the distinct positive two-digit factors of 144 is:\\\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.\",\n", - " \"round\": 0,\n", - " \"messages\": [\n", - " {\n", - " \"content\": \"What is the sum of all the distinct positive two-digit factors of 144?\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere's a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\\\"The sum of all the distinct positive two-digit factors of 144 is:\\\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 11.140539407730103,\n", - " \"trial\": -1\n", - "}\n", - "\n", - "Task failed example: {\n", - " \"problem\": \"Find all $x$ that satisfy the inequality $(2x+10)(x+3)<(3x+9)(x+8)$. Express your answer in interval notation.\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Algebra\",\n", - " \"solution\": \"We have \\\\begin{align*} (2x+10)(x+3)&<(3x+9)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)&<3(x+3)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)-3(x+3)(x+8)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (2x+10-(3x+24))(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (-x-14)(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (x+14)(x+3)&>0.\\n\\\\end{align*} This inequality is satisfied if and only if $(x+14)$ and $(x+3)$ are either both positive or both negative. Both factors are positive for $x>-3$ and both factors are negative for $x<-14$. When $-14 0$ and then find intervals where the inequality holds true.\\n\\nLet's first find the factorized form of the quadratic:\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"I apologize for the confusion in my previous message. Let's find the factorized form of the quadratic using Python code.\\n\\n```python\\nfrom sympy import symbols, factor\\r\\nx = symbols('x')\\r\\nfactorized = factor(x**2 + 17*x + 42)\\r\\nfactorized\\r\\n```\\n\\nPlease run the Python code above to find the factorized form of the quadratic, and we can proceed to determine the intervals where the inequality holds true.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 24.91333508491516,\n", - " \"trial\": -1\n", - "}\n", - "\n", - "Evaluation dictionary: {\n", - " \"Problem Interpretation\": {\n", - " \"description\": \"Ability to correctly interpret the problem.\",\n", - " \"accepted_values\": [\"completely off\", \"slightly relevant\", \"relevant\", \"mostly accurate\", \"completely accurate\"]\n", - " },\n", - " \"Mathematical Methodology\": {\n", - " \"description\": \"Adequacy of the chosen mathematical or algorithmic methodology for the question\",\n", - " \"accepted_values\": [\"inappropriate\", \"barely adequate\", \"adequate\", \"mostly effective\", \"completely effective\"]\n", - " },\n", - " \"Calculation Correctness\": {\n", - " \"description\": \"Accuracy of calculations made and solutions given\",\n", - " \"accepted_values\": [\"completely incorrect\", \"mostly incorrect\", \"neither\", \"mostly correct\", \"completely correct\"]\n", - " },\n", - " \"Explanation Clarity\": {\n", - " \"description\": \"Clarity and comprehensibility of explanations, including language use and structure\",\n", - " \"accepted_values\": [\"not at all clear\", \"slightly clear\", \"moderately clear\", \"very clear\", \"completely clear\"]\n", - " },\n", - " \"Code Efficiency\": {\n", - " \"description\": \"Quality of code in terms of efficiency and elegance\",\n", - " \"accepted_values\": [\"not at all efficient\", \"slightly efficient\", \"moderately efficient\", \"very efficient\", \"extremely efficient\"]\n", - " },\n", - " \"Code Correctness\": {\n", - " \"description\": \"Correctness of the provided code\",\n", - " \"accepted_values\": [\"completely incorrect\", \"mostly incorrect\", \"partly correct\", \"mostly correct\", \"completely correct\"]\n", - " }\n", + " Task description: Given any question, the system needs to solve the problem as consisely and accurately as possible\n", + " Task successful example: {'problem': 'What is the sum of all the distinct positive two-digit factors of 144?', 'level': 'Level 5', 'type': 'Number Theory', 'solution': 'Prime factorize $144=2^4\\\\cdot3^2$. The sum of the positive two-digit factors of 144 is $2^4+2\\\\cdot3^2+2^2\\\\cdot3+2^2\\\\cdot3^2+2^3\\\\cdot3+2^3\\\\cdot3^2+2^4\\\\cdot3=\\\\boxed{226}.$', 'problem_id': '0', 'response_with_ans': 'To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere\\'s a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\"The sum of all the distinct positive two-digit factors of 144 is:\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.', 'round': 0, 'messages': [{'content': 'What is the sum of all the distinct positive two-digit factors of 144?', 'role': 'user'}, {'content': 'To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere\\'s a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\"The sum of all the distinct positive two-digit factors of 144 is:\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.', 'role': 'assistant'}], 'time': 11.140539407730103, 'trial': -1}\n", + " Task failed example: {'problem': 'Find all $x$ that satisfy the inequality $(2x+10)(x+3)<(3x+9)(x+8)$. Express your answer in interval notation.', 'level': 'Level 5', 'type': 'Algebra', 'solution': 'We have \\\\begin{align*} (2x+10)(x+3)&<(3x+9)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)&<3(x+3)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)-3(x+3)(x+8)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (2x+10-(3x+24))(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (-x-14)(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (x+14)(x+3)&>0.\\n\\\\end{align*} This inequality is satisfied if and only if $(x+14)$ and $(x+3)$ are either both positive or both negative. Both factors are positive for $x>-3$ and both factors are negative for $x<-14$. When $-14 0$ and then find intervals where the inequality holds true.\\n\\nLet's first find the factorized form of the quadratic:\", 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': \"I apologize for the confusion in my previous message. Let's find the factorized form of the quadratic using Python code.\\n\\n```python\\nfrom sympy import symbols, factor\\r\\nx = symbols('x')\\r\\nfactorized = factor(x**2 + 17*x + 42)\\r\\nfactorized\\r\\n```\\n\\nPlease run the Python code above to find the factorized form of the quadratic, and we can proceed to determine the intervals where the inequality holds true.\", 'role': 'assistant'}], 'time': 24.91333508491516, 'trial': -1}\n", + " Evaluation dictionary: [\n", + " {\n", + " \"name\": \"Problem Interpretation\",\n", + " \"description\": \"Ability to correctly interpret the problem.\",\n", + " \"accepted_values\": [\n", + " \"completely off\",\n", + " \"slightly relevant\",\n", + " \"relevant\",\n", + " \"mostly accurate\",\n", + " \"completely accurate\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Mathematical Methodology\",\n", + " \"description\": \"Adequacy of the chosen mathematical or algorithmic methodology for the question\",\n", + " \"accepted_values\": [\n", + " \"inappropriate\",\n", + " \"barely adequate\",\n", + " \"adequate\",\n", + " \"mostly effective\",\n", + " \"completely effective\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Calculation Correctness\",\n", + " \"description\": \"Accuracy of calculations made and solutions given\",\n", + " \"accepted_values\": [\n", + " \"completely incorrect\",\n", + " \"mostly incorrect\",\n", + " \"neither\",\n", + " \"mostly correct\",\n", + " \"completely correct\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Explanation Clarity\",\n", + " \"description\": \"Clarity and comprehensibility of explanations, including language use and structure\",\n", + " \"accepted_values\": [\n", + " \"not at all clear\",\n", + " \"slightly clear\",\n", + " \"moderately clear\",\n", + " \"very clear\",\n", + " \"completely clear\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Code Efficiency\",\n", + " \"description\": \"Quality of code in terms of efficiency and elegance\",\n", + " \"accepted_values\": [\n", + " \"not at all efficient\",\n", + " \"slightly efficient\",\n", + " \"moderately efficient\",\n", + " \"very efficient\",\n", + " \"extremely efficient\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Code Correctness\",\n", + " \"description\": \"Correctness of the provided code\",\n", + " \"accepted_values\": [\n", + " \"completely incorrect\",\n", + " \"mostly incorrect\",\n", + " \"partly correct\",\n", + " \"mostly correct\",\n", + " \"completely correct\"\n", + " ],\n", + " \"sub_criteria\": []\n", " }\n", - "actual test case to evaluate: {\n", - " \"problem\": \"What is the product of $0.\\\\overline{05}$ and $1.8$ expressed as a fraction in simplest form?\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Prealgebra\",\n", - " \"solution\": \"First let's convert $0.\\\\overline{05}$ to a fraction. Let's define $x=0.\\\\overline{05}$. If we multiply both sides by $100$ we get $100x=5.\\\\overline{05}$ so $99x=5$ and $x=0.\\\\overline{05}=\\\\frac{5}{99}$. Since $1.8=\\\\frac{9}{5}$ we get $$\\\\frac{\\\\cancel{5}}{99}\\\\cdot\\\\frac{9}{\\\\cancel{5}}=\\\\frac{9}{99}=\\\\boxed{\\\\frac{1}{11}}.$$\",\n", - " \"problem_id\": \"9\",\n", - " \"response_with_ans\": \"```python\\nfrom fractions import Fraction\\n\\nfraction_1 = Fraction(5, 99)\\ndecimal_2 = 1.8\\n\\nproduct = fraction_1 * decimal_2\\n\\nsimplified_fraction = Fraction(product).limit_denominator()\\n\\nprint(simplified_fraction)\\n```\\n\\nPlease run the above Python code to find the product of $\\\\frac{5}{99}$ and $1.8$ expressed as a fraction in simplest form.\",\n", - " \"round\": 1,\n", - " \"messages\": [\n", - " {\n", - " \"content\": \"What is the product of $0.\\\\overline{05}$ and $1.8$ expressed as a fraction in simplest form?\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"To find the product of $0.\\\\overline{05}$ and $1.8$, first we need to convert the repeating decimal $0.\\\\overline{05}$ into a fraction.\\n\\nLet's call the repeating decimal $x$:\\n\\n$$x = 0.\\\\overline{05}$$\\n\\nTo convert the repeating decimal into a fraction, we can multiply both sides by $100$ to shift the decimal point two places to the right:\\n\\n$$100x = 5.\\\\overline{05}$$\\n\\nNow, subtract the first equation from the second equation:\\n\\n$$100x - x = 5.\\\\overline{05} - 0.\\\\overline{05}$$\\n$$99x = 5$$\\n\\nNow, we can solve for $x$:\\n\\n$$x = \\\\frac{5}{99}$$\\n\\nSo the repeating decimal $0.\\\\overline{05}$ is equivalent to the fraction $\\\\frac{5}{99}$.\\n\\nNow, we can find the product of $\\\\frac{5}{99}$ and $1.8$:\\n\\n$$\\\\frac{5}{99} \\\\times 1.8$$\\n\\nFinally, we will simplify the fraction. Let's write the code to compute the product and simplify the result as a fraction.\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"```python\\nfrom fractions import Fraction\\n\\nfraction_1 = Fraction(5, 99)\\ndecimal_2 = 1.8\\n\\nproduct = fraction_1 * decimal_2\\n\\nsimplified_fraction = Fraction(product).limit_denominator()\\n\\nprint(simplified_fraction)\\n```\\n\\nPlease run the above Python code to find the product of $\\\\frac{5}{99}$ and $1.8$ expressed as a fraction in simplest form.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 18.04914665222168,\n", - " \"trial\": -1\n", - "}\n", + "]actual test case to evaluate: {'problem': \"Amaretta's birthday is July 27, and her brother Enzo's birthday is September 3. Every year, Amaretta and Enzo celebrate by eating cake every day from Amaretta's birthday through Enzo's birthday (including both birthdays). If they did this for the first time in 2008, how many cake-eating days will they have observed by the end of 2016?\", 'level': 'Level 5', 'type': 'Prealgebra', 'solution': 'There are $39$ cake-eating days each year: the last $5$ days of July, all $31$ days of August, and the first $3$ days of September.\\n\\nThere are $9$ years in the list $$2008,2009,2010,2011,2012,2013,2014,2015,2016.$$ Besides listing them out, we can also see this by subtracting $2007$ from each year, which gives us the list $1,2,3,4,5,6,7,8,9$ (which clearly has $9$ entries).\\n\\n$39$ cake-eating days each year for $9$ years make $39\\\\cdot 9 = \\\\boxed{351}$ days in total.', 'problem_id': '3', 'response_with_ans': \"To calculate the total number of cake-eating days, we will first calculate the number of days between Amaretta's birthday and Enzo's birthday in a non-leap year and in a leap year. Then, we will count the number of leap years and non-leap years in the given range (2008-2016). Finally, we will sum the total number of days for each type of year for both non-leap and leap years.\\n\\nPlease run the following Python code:\\n\\n```python\\nfrom datetime import date\\n\\namaretta_birthday = date(2008, 7, 27)\\nenzo_birthday = date(2008, 9, 3)\\n\\nnormal_year_days = (enzo_birthday - amaretta_birthday).days + 1\\nleap_year_days = normal_year_days + 1\\n\\nleap_years = [year for year in range(2008, 2017) if year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)]\\nnon_leap_years_count = 9 - len(leap_years)\\n\\ntotal_cake_eating_days = non_leap_years_count * normal_year_days + len(leap_years) * leap_year_days\\nprint(total_cake_eating_days)\\n```\\n\", 'round': 0, 'messages': [{'content': \"Amaretta's birthday is July 27, and her brother Enzo's birthday is September 3. Every year, Amaretta and Enzo celebrate by eating cake every day from Amaretta's birthday through Enzo's birthday (including both birthdays). If they did this for the first time in 2008, how many cake-eating days will they have observed by the end of 2016?\", 'role': 'user'}, {'content': \"To calculate the total number of cake-eating days, we will first calculate the number of days between Amaretta's birthday and Enzo's birthday in a non-leap year and in a leap year. Then, we will count the number of leap years and non-leap years in the given range (2008-2016). Finally, we will sum the total number of days for each type of year for both non-leap and leap years.\\n\\nPlease run the following Python code:\\n\\n```python\\nfrom datetime import date\\n\\namaretta_birthday = date(2008, 7, 27)\\nenzo_birthday = date(2008, 9, 3)\\n\\nnormal_year_days = (enzo_birthday - amaretta_birthday).days + 1\\nleap_year_days = normal_year_days + 1\\n\\nleap_years = [year for year in range(2008, 2017) if year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)]\\nnon_leap_years_count = 9 - len(leap_years)\\n\\ntotal_cake_eating_days = non_leap_years_count * normal_year_days + len(leap_years) * leap_year_days\\nprint(total_cake_eating_days)\\n```\\n\", 'role': 'assistant'}], 'time': 15.208062410354614, 'trial': -1}\n", "\n", "--------------------------------------------------------------------------------\n", + "\u001b[31m\n", + ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", "\u001b[33mquantifier\u001b[0m (to quantifier_user):\n", "\n", "{\n", @@ -1636,1660 +1232,1254 @@ "}\n", "\n", "--------------------------------------------------------------------------------\n", - "actual label for this case: false\n", "\u001b[33mquantifier_user\u001b[0m (to quantifier):\n", "\n", "Task: Math problem solving.\n", - "Task description: Given any question, the system needs to solve the problem as consisely and accurately as possible\n", - "Task successful example: {\n", - " \"problem\": \"What is the sum of all the distinct positive two-digit factors of 144?\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Number Theory\",\n", - " \"solution\": \"Prime factorize $144=2^4\\\\cdot3^2$. The sum of the positive two-digit factors of 144 is $2^4+2\\\\cdot3^2+2^2\\\\cdot3+2^2\\\\cdot3^2+2^3\\\\cdot3+2^3\\\\cdot3^2+2^4\\\\cdot3=\\\\boxed{226}.$\",\n", - " \"problem_id\": \"0\",\n", - " \"response_with_ans\": \"To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere's a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\\\"The sum of all the distinct positive two-digit factors of 144 is:\\\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.\",\n", - " \"round\": 0,\n", - " \"messages\": [\n", - " {\n", - " \"content\": \"What is the sum of all the distinct positive two-digit factors of 144?\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere's a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\\\"The sum of all the distinct positive two-digit factors of 144 is:\\\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 11.140539407730103,\n", - " \"trial\": -1\n", - "}\n", - "\n", - "Task failed example: {\n", - " \"problem\": \"Find all $x$ that satisfy the inequality $(2x+10)(x+3)<(3x+9)(x+8)$. Express your answer in interval notation.\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Algebra\",\n", - " \"solution\": \"We have \\\\begin{align*} (2x+10)(x+3)&<(3x+9)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)&<3(x+3)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)-3(x+3)(x+8)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (2x+10-(3x+24))(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (-x-14)(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (x+14)(x+3)&>0.\\n\\\\end{align*} This inequality is satisfied if and only if $(x+14)$ and $(x+3)$ are either both positive or both negative. Both factors are positive for $x>-3$ and both factors are negative for $x<-14$. When $-14 0$ and then find intervals where the inequality holds true.\\n\\nLet's first find the factorized form of the quadratic:\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"I apologize for the confusion in my previous message. Let's find the factorized form of the quadratic using Python code.\\n\\n```python\\nfrom sympy import symbols, factor\\r\\nx = symbols('x')\\r\\nfactorized = factor(x**2 + 17*x + 42)\\r\\nfactorized\\r\\n```\\n\\nPlease run the Python code above to find the factorized form of the quadratic, and we can proceed to determine the intervals where the inequality holds true.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 24.91333508491516,\n", - " \"trial\": -1\n", - "}\n", - "\n", - "Evaluation dictionary: {\n", - " \"Problem Interpretation\": {\n", - " \"description\": \"Ability to correctly interpret the problem.\",\n", - " \"accepted_values\": [\"completely off\", \"slightly relevant\", \"relevant\", \"mostly accurate\", \"completely accurate\"]\n", - " },\n", - " \"Mathematical Methodology\": {\n", - " \"description\": \"Adequacy of the chosen mathematical or algorithmic methodology for the question\",\n", - " \"accepted_values\": [\"inappropriate\", \"barely adequate\", \"adequate\", \"mostly effective\", \"completely effective\"]\n", - " },\n", - " \"Calculation Correctness\": {\n", - " \"description\": \"Accuracy of calculations made and solutions given\",\n", - " \"accepted_values\": [\"completely incorrect\", \"mostly incorrect\", \"neither\", \"mostly correct\", \"completely correct\"]\n", - " },\n", - " \"Explanation Clarity\": {\n", - " \"description\": \"Clarity and comprehensibility of explanations, including language use and structure\",\n", - " \"accepted_values\": [\"not at all clear\", \"slightly clear\", \"moderately clear\", \"very clear\", \"completely clear\"]\n", - " },\n", - " \"Code Efficiency\": {\n", - " \"description\": \"Quality of code in terms of efficiency and elegance\",\n", - " \"accepted_values\": [\"not at all efficient\", \"slightly efficient\", \"moderately efficient\", \"very efficient\", \"extremely efficient\"]\n", - " },\n", - " \"Code Correctness\": {\n", - " \"description\": \"Correctness of the provided code\",\n", - " \"accepted_values\": [\"completely incorrect\", \"mostly incorrect\", \"partly correct\", \"mostly correct\", \"completely correct\"]\n", - " }\n", + " Task description: Given any question, the system needs to solve the problem as consisely and accurately as possible\n", + " Task successful example: {'problem': 'What is the sum of all the distinct positive two-digit factors of 144?', 'level': 'Level 5', 'type': 'Number Theory', 'solution': 'Prime factorize $144=2^4\\\\cdot3^2$. The sum of the positive two-digit factors of 144 is $2^4+2\\\\cdot3^2+2^2\\\\cdot3+2^2\\\\cdot3^2+2^3\\\\cdot3+2^3\\\\cdot3^2+2^4\\\\cdot3=\\\\boxed{226}.$', 'problem_id': '0', 'response_with_ans': 'To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere\\'s a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\"The sum of all the distinct positive two-digit factors of 144 is:\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.', 'round': 0, 'messages': [{'content': 'What is the sum of all the distinct positive two-digit factors of 144?', 'role': 'user'}, {'content': 'To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere\\'s a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\"The sum of all the distinct positive two-digit factors of 144 is:\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.', 'role': 'assistant'}], 'time': 11.140539407730103, 'trial': -1}\n", + " Task failed example: {'problem': 'Find all $x$ that satisfy the inequality $(2x+10)(x+3)<(3x+9)(x+8)$. Express your answer in interval notation.', 'level': 'Level 5', 'type': 'Algebra', 'solution': 'We have \\\\begin{align*} (2x+10)(x+3)&<(3x+9)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)&<3(x+3)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)-3(x+3)(x+8)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (2x+10-(3x+24))(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (-x-14)(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (x+14)(x+3)&>0.\\n\\\\end{align*} This inequality is satisfied if and only if $(x+14)$ and $(x+3)$ are either both positive or both negative. Both factors are positive for $x>-3$ and both factors are negative for $x<-14$. When $-14 0$ and then find intervals where the inequality holds true.\\n\\nLet's first find the factorized form of the quadratic:\", 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': \"I apologize for the confusion in my previous message. Let's find the factorized form of the quadratic using Python code.\\n\\n```python\\nfrom sympy import symbols, factor\\r\\nx = symbols('x')\\r\\nfactorized = factor(x**2 + 17*x + 42)\\r\\nfactorized\\r\\n```\\n\\nPlease run the Python code above to find the factorized form of the quadratic, and we can proceed to determine the intervals where the inequality holds true.\", 'role': 'assistant'}], 'time': 24.91333508491516, 'trial': -1}\n", + " Evaluation dictionary: [\n", + " {\n", + " \"name\": \"Problem Interpretation\",\n", + " \"description\": \"Ability to correctly interpret the problem.\",\n", + " \"accepted_values\": [\n", + " \"completely off\",\n", + " \"slightly relevant\",\n", + " \"relevant\",\n", + " \"mostly accurate\",\n", + " \"completely accurate\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Mathematical Methodology\",\n", + " \"description\": \"Adequacy of the chosen mathematical or algorithmic methodology for the question\",\n", + " \"accepted_values\": [\n", + " \"inappropriate\",\n", + " \"barely adequate\",\n", + " \"adequate\",\n", + " \"mostly effective\",\n", + " \"completely effective\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Calculation Correctness\",\n", + " \"description\": \"Accuracy of calculations made and solutions given\",\n", + " \"accepted_values\": [\n", + " \"completely incorrect\",\n", + " \"mostly incorrect\",\n", + " \"neither\",\n", + " \"mostly correct\",\n", + " \"completely correct\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Explanation Clarity\",\n", + " \"description\": \"Clarity and comprehensibility of explanations, including language use and structure\",\n", + " \"accepted_values\": [\n", + " \"not at all clear\",\n", + " \"slightly clear\",\n", + " \"moderately clear\",\n", + " \"very clear\",\n", + " \"completely clear\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Code Efficiency\",\n", + " \"description\": \"Quality of code in terms of efficiency and elegance\",\n", + " \"accepted_values\": [\n", + " \"not at all efficient\",\n", + " \"slightly efficient\",\n", + " \"moderately efficient\",\n", + " \"very efficient\",\n", + " \"extremely efficient\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Code Correctness\",\n", + " \"description\": \"Correctness of the provided code\",\n", + " \"accepted_values\": [\n", + " \"completely incorrect\",\n", + " \"mostly incorrect\",\n", + " \"partly correct\",\n", + " \"mostly correct\",\n", + " \"completely correct\"\n", + " ],\n", + " \"sub_criteria\": []\n", " }\n", - "actual test case to evaluate: {\n", - " \"problem\": \"What is $.0\\\\overline{3} \\\\div .\\\\overline{03}$? Express your answer as a mixed number.\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Prealgebra\",\n", - " \"solution\": \"It is almost always easier to use fractions than decimals when dividing. So the first task is to convert these repeating decimals to fractions. First, $.0\\\\overline{3}$: \\\\[\\n10 \\\\cdot .0\\\\overline{3} = .\\\\overline{3} = \\\\frac{1}{3}\\\\\\\\\\n\\\\Rightarrow .0\\\\overline{3} = \\\\frac{1}{3} \\\\div 10 = \\\\frac{1}{3} \\\\cdot \\\\frac{1}{10} = \\\\frac{1}{30}.\\n\\\\]Next, $.\\\\overline{03}$: \\\\[\\n99 \\\\cdot .\\\\overline{03} = (100-1) \\\\cdot .\\\\overline{03} = 3.\\\\overline{03} - .\\\\overline{03} = 3\\\\\\\\\\n\\\\Rightarrow .\\\\overline{03} = \\\\frac{3}{99} = \\\\frac{3}{3 \\\\cdot 33} = \\\\frac{1}{33}.\\n\\\\]We now have the tools to make our calculation: \\\\begin{align*}\\n.0\\\\overline{3} \\\\div .\\\\overline{03} &= \\\\frac{1}{30} \\\\div \\\\frac{1}{33}= \\\\frac{1}{30} \\\\cdot \\\\frac{33}{1}\\\\\\\\\\n&= \\\\frac{33}{30} = \\\\frac{3 \\\\cdot 11}{3 \\\\cdot 10} = \\\\frac{11}{10}\\\\\\\\\\n&= \\\\frac{10+1}{10} = \\\\boxed{1\\\\frac{1}{10}}.\\n\\\\end{align*}\",\n", - " \"problem_id\": \"15\",\n", - " \"response_with_ans\": \"Now, let's simplify the fraction:\\n\\n$\\\\frac{33}{3} = 11$\\n\\nSo, the result of the division is:\\n\\n$.0\\\\overline{3} \\\\div .\\\\overline{03} = 11$\\n\\nSince there is no remainder or mixed number component, the final answer is 11.\\n\\nTERMINATE\",\n", - " \"round\": 3,\n", - " \"messages\": [\n", - " {\n", - " \"content\": \"What is $.0\\\\overline{3} \\\\div .\\\\overline{03}$? Express your answer as a mixed number.\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"To solve the problem, let's first convert the repeating decimal numbers into fractions.\\n\\n1. Convert $0.\\\\overline{3}$ to a fraction:\\n\\nLet x = $0.\\\\overline{3}$\\nThen, 10x = $3.\\\\overline{3}$\\nSubtracting the two equations, we get:\\n9x = 3\\nx = $\\\\frac{1}{3}$\\n\\nSo, $0.\\\\overline{3}$ can be written as $\\\\frac{1}{3}$.\\n\\n2. Convert $.\\\\overline{03}$ to a fraction:\\n\\nLet y = $.\\\\overline{03}$\\nThen, 100y = $3.\\\\overline{03}$\\nSubtracting the two equations, we get:\\n99y = 3\\ny = $\\\\frac{1}{33}$\\n\\nSo, $.\\\\overline{03}$ can be written as $\\\\frac{1}{33}$.\\n\\nNow that we have the fractions, we can compute: \\n$(\\\\frac{1}{3}) \\\\div (\\\\frac{1}{33})$\\nTo divide fractions, we multiply the first one by the reciprocal of the second one:\\n\\n$(\\\\frac{1}{3}) \\\\times (\\\\frac{33}{1})$\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"Let's compute the product of the fractions:\\n\\n$\\\\frac{1}{3} \\\\times \\\\frac{33}{1} = \\\\frac{1 \\\\times 33}{3 \\\\times 1}$\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"Now, let's solve the multiplication in the numerator and the denominator:\\n\\n$\\\\frac{1 \\\\times 33}{3 \\\\times 1} = \\\\frac{33}{3}$\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"Now, let's simplify the fraction:\\n\\n$\\\\frac{33}{3} = 11$\\n\\nSo, the result of the division is:\\n\\n$.0\\\\overline{3} \\\\div .\\\\overline{03} = 11$\\n\\nSince there is no remainder or mixed number component, the final answer is 11.\\n\\nTERMINATE\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 34.40860724449158,\n", - " \"trial\": -1\n", - "}\n", + "]actual test case to evaluate: {'problem': 'In the diagram, $AB,$ $BC,$ $CD,$ $DE,$ $EF,$ $FG,$ $GH,$ and $HK$ all have length $4,$ and all angles are right angles, with the exception of the angles at $D$ and $F.$\\n\\n[asy]\\ndraw((0,0)--(0,4)--(4,4)--(4,8)--(6.8284,5.1716)--(9.6569,8)--(9.6569,4)--(13.6569,4)--(13.6569,0)--cycle,black+linewidth(1));\\ndraw((0,0)--(0.5,0)--(0.5,0.5)--(0,0.5)--cycle,black+linewidth(1));\\ndraw((0,4)--(0.5,4)--(0.5,3.5)--(0,3.5)--cycle,black+linewidth(1));\\ndraw((4,4)--(4,4.5)--(3.5,4.5)--(3.5,4)--cycle,black+linewidth(1));\\ndraw((6.8284,5.1716)--(7.0784,5.4216)--(6.8284,5.6716)--(6.5784,5.4216)--cycle,black+linewidth(1));\\ndraw((9.6569,4)--(10.1569,4)--(10.1569,4.5)--(9.6569,4.5)--cycle,black+linewidth(1));\\ndraw((13.6569,4)--(13.1569,4)--(13.1569,3.5)--(13.6569,3.5)--cycle,black+linewidth(1));\\ndraw((13.6569,0)--(13.1569,0)--(13.1569,0.5)--(13.6569,0.5)--cycle,black+linewidth(1));\\nlabel(\"$A$\",(0,0),W);\\nlabel(\"$B$\",(0,4),NW);\\nlabel(\"$C$\",(4,4),S);\\nlabel(\"$D$\",(4,8),N);\\nlabel(\"$E$\",(6.8284,5.1716),S);\\nlabel(\"$F$\",(9.6569,8),N);\\nlabel(\"$G$\",(9.6569,4),S);\\nlabel(\"$H$\",(13.6569,4),NE);\\nlabel(\"$K$\",(13.6569,0),E);\\n[/asy]\\n\\nDetermine the length of $DF.$\\n\\n[asy]\\ndraw((0,0)--(2.8284,-2.8284)--(5.6568,0),black+linewidth(1));\\ndraw((0,0)--(5.6568,0),black+linewidth(1)+dashed);\\ndraw((2.8284,-2.8284)--(3.0784,-2.5784)--(2.8284,-2.3284)--(2.5784,-2.5784)--cycle,black+linewidth(1));\\nlabel(\"$D$\",(0,0),N);\\nlabel(\"$E$\",(2.8284,-2.8284),S);\\nlabel(\"$F$\",(5.6568,0),N);\\n[/asy]', 'level': 'Level 5', 'type': 'Prealgebra', 'solution': 'Since $DE=EF=4$ and $\\\\angle DEF = 90^\\\\circ,$ by the Pythagorean Theorem, \\\\begin{align*}\\nDF^2 &= DE^2+EF^2 \\\\\\\\\\n&= 4^2+4^2 \\\\\\\\\\n&=32,\\n\\\\end{align*}so that $DF = \\\\sqrt{32}=\\\\boxed{4\\\\sqrt{2}}.$', 'problem_id': '16', 'response_with_ans': \"Now let's calculate the square of DF using Python.\\n\\n```python\\nDH = 9.6569\\nHG = 5.6569\\ncos_alpha_beta = 0\\n\\nDF_squared = DH**2 + HG**2 - 2 * DH * HG * cos_alpha_beta\\nDF_squared\\n```\", 'round': 2, 'messages': [{'content': 'In the diagram, $AB,$ $BC,$ $CD,$ $DE,$ $EF,$ $FG,$ $GH,$ and $HK$ all have length $4,$ and all angles are right angles, with the exception of the angles at $D$ and $F.$\\n\\n[asy]\\ndraw((0,0)--(0,4)--(4,4)--(4,8)--(6.8284,5.1716)--(9.6569,8)--(9.6569,4)--(13.6569,4)--(13.6569,0)--cycle,black+linewidth(1));\\ndraw((0,0)--(0.5,0)--(0.5,0.5)--(0,0.5)--cycle,black+linewidth(1));\\ndraw((0,4)--(0.5,4)--(0.5,3.5)--(0,3.5)--cycle,black+linewidth(1));\\ndraw((4,4)--(4,4.5)--(3.5,4.5)--(3.5,4)--cycle,black+linewidth(1));\\ndraw((6.8284,5.1716)--(7.0784,5.4216)--(6.8284,5.6716)--(6.5784,5.4216)--cycle,black+linewidth(1));\\ndraw((9.6569,4)--(10.1569,4)--(10.1569,4.5)--(9.6569,4.5)--cycle,black+linewidth(1));\\ndraw((13.6569,4)--(13.1569,4)--(13.1569,3.5)--(13.6569,3.5)--cycle,black+linewidth(1));\\ndraw((13.6569,0)--(13.1569,0)--(13.1569,0.5)--(13.6569,0.5)--cycle,black+linewidth(1));\\nlabel(\"$A$\",(0,0),W);\\nlabel(\"$B$\",(0,4),NW);\\nlabel(\"$C$\",(4,4),S);\\nlabel(\"$D$\",(4,8),N);\\nlabel(\"$E$\",(6.8284,5.1716),S);\\nlabel(\"$F$\",(9.6569,8),N);\\nlabel(\"$G$\",(9.6569,4),S);\\nlabel(\"$H$\",(13.6569,4),NE);\\nlabel(\"$K$\",(13.6569,0),E);\\n[/asy]\\n\\nDetermine the length of $DF.$\\n\\n[asy]\\ndraw((0,0)--(2.8284,-2.8284)--(5.6568,0),black+linewidth(1));\\ndraw((0,0)--(5.6568,0),black+linewidth(1)+dashed);\\ndraw((2.8284,-2.8284)--(3.0784,-2.5784)--(2.8284,-2.3284)--(2.5784,-2.5784)--cycle,black+linewidth(1));\\nlabel(\"$D$\",(0,0),N);\\nlabel(\"$E$\",(2.8284,-2.8284),S);\\nlabel(\"$F$\",(5.6568,0),N);\\n[/asy]', 'role': 'user'}, {'content': \"Let's use coordinate geometry to determine the coordinates of points D, E, and F, which will allow us to compute the Euclidean distance between points D and F.\\n\\n1. Assign coordinates to points A, B, C, D, E, F, G, H, and K.\\n2. Calculate the slope and equation of line DE and line FG.\\n3. Use the slopes and the coordinates of points E and G to compute the coordinates of point F.\\n4. Calculate the length of DF using the distance formula.\\n\\nLet's start by assigning coordinates:\\n\\n1. A(0, 0), B(0, 4), C(4, 4), D(4, 8), G(9.6569, 4), H(13.6569, 4), and K(13.6569, 0).\", 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': \"Since the angles at points D and F are not right angles, we need to use some trigonometry to find points E and F. We already have the coordinates of points D and G.\\n\\nLet Ξ± be the angle between the horizontal line and the line DE, and Ξ² be the angle between the horizontal line and the line FG.\\n\\nLet P be the intersection point between the horizontal lines passing through points D and F. Therefore, the triangle DPF is a right triangle. \\n\\nSince we know the angles Ξ±, Ξ², and the length DG, we can determine the length DF using the law of cosines:\\n\\nLet's calculate the length of DG first:\\n\\nDG = DH + HG = (13.6569 - 4) + (9.6569 - 4) = 9.6569 + 5.6569 = 15.3138\\n\\nNow, let Ξ± be the angle between line DH and DF, and Ξ² be the angle between line HG and DF. We can use the law of cosines to calculate the length of DF:\\n\\nDF^2 = DH^2 + HG^2 - 2 * DH * HG * cos(Ξ± + Ξ²)\\n\\nWe also know angle DAB = Ξ±, angle GHB = Ξ² and AB = FG = 4.\\n\\nWe can calculate cos(Ξ± + Ξ²) using the cosine formula for the addition of angles:\\n\\ncos(Ξ± + Ξ²) = cos(Ξ±) * cos(Ξ²) - sin(Ξ±) * sin(Ξ²)\\n\\nTo find sin(Ξ±) and cos(Ξ±), we can use right triangle with sides DA, AB, and DB. Since DB = BA = 4, we have:\\n\\ncos(Ξ±) = AD / DB = 4 / 4 = 1\\nsin(Ξ±) = BA / DB = 4 / 4 = 1\\n\\nSimilarly, for angle Ξ², we have right triangle with sides GH, HB, and GB. Since GB = BH = 4, we have:\\n\\ncos(Ξ²) = HG / GB = 4 / 4 = 1\\nsin(Ξ²) = HB / GB = 4 / 4 = 1\\n\\nNow, let's calculate cos(Ξ± + Ξ²):\\n\\ncos(Ξ± + Ξ²) = cos(Ξ±) * cos(Ξ²) - sin(Ξ±) * sin(Ξ²) = 1 * 1 - 1 * 1 = 0\\n\\nFinally, let's calculate DF using the law of cosines:\\n\\nDF^2 = DH^2 + HG^2 - 2 * DH * HG * cos(Ξ± + Ξ²)\\nDF^2 = 9.6569^2 + 5.6569^2 - 2 * 9.6569 * 5.6569 * 0\\n\\nNow let's compute the square of DF.\", 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': \"Now let's calculate the square of DF using Python.\\n\\n```python\\nDH = 9.6569\\nHG = 5.6569\\ncos_alpha_beta = 0\\n\\nDF_squared = DH**2 + HG**2 - 2 * DH * HG * cos_alpha_beta\\nDF_squared\\n```\", 'role': 'assistant'}], 'time': 44.82826566696167, 'trial': -1}\n", "\n", "--------------------------------------------------------------------------------\n", + "\u001b[31m\n", + ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", "\u001b[33mquantifier\u001b[0m (to quantifier_user):\n", "\n", + "```json\n", "{\n", " \"Problem Interpretation\": \"completely accurate\",\n", - " \"Mathematical Methodology\": \"mostly effective\",\n", + " \"Mathematical Methodology\": \"inappropriate\",\n", " \"Calculation Correctness\": \"completely incorrect\",\n", " \"Explanation Clarity\": \"moderately clear\",\n", - " \"Code Efficiency\": \"not applicable\",\n", - " \"Code Correctness\": \"not applicable\"\n", + " \"Code Efficiency\": \"not at all efficient\",\n", + " \"Code Correctness\": \"completely incorrect\"\n", "}\n", + "```\n", "\n", "--------------------------------------------------------------------------------\n", - "actual label for this case: true\n", "\u001b[33mquantifier_user\u001b[0m (to quantifier):\n", "\n", "Task: Math problem solving.\n", - "Task description: Given any question, the system needs to solve the problem as consisely and accurately as possible\n", - "Task successful example: {\n", - " \"problem\": \"What is the sum of all the distinct positive two-digit factors of 144?\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Number Theory\",\n", - " \"solution\": \"Prime factorize $144=2^4\\\\cdot3^2$. The sum of the positive two-digit factors of 144 is $2^4+2\\\\cdot3^2+2^2\\\\cdot3+2^2\\\\cdot3^2+2^3\\\\cdot3+2^3\\\\cdot3^2+2^4\\\\cdot3=\\\\boxed{226}.$\",\n", - " \"problem_id\": \"0\",\n", - " \"response_with_ans\": \"To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere's a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\\\"The sum of all the distinct positive two-digit factors of 144 is:\\\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.\",\n", - " \"round\": 0,\n", - " \"messages\": [\n", - " {\n", - " \"content\": \"What is the sum of all the distinct positive two-digit factors of 144?\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere's a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\\\"The sum of all the distinct positive two-digit factors of 144 is:\\\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 11.140539407730103,\n", - " \"trial\": -1\n", - "}\n", - "\n", - "Task failed example: {\n", - " \"problem\": \"Find all $x$ that satisfy the inequality $(2x+10)(x+3)<(3x+9)(x+8)$. Express your answer in interval notation.\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Algebra\",\n", - " \"solution\": \"We have \\\\begin{align*} (2x+10)(x+3)&<(3x+9)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)&<3(x+3)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)-3(x+3)(x+8)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (2x+10-(3x+24))(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (-x-14)(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (x+14)(x+3)&>0.\\n\\\\end{align*} This inequality is satisfied if and only if $(x+14)$ and $(x+3)$ are either both positive or both negative. Both factors are positive for $x>-3$ and both factors are negative for $x<-14$. When $-14 0$ and then find intervals where the inequality holds true.\\n\\nLet's first find the factorized form of the quadratic:\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"I apologize for the confusion in my previous message. Let's find the factorized form of the quadratic using Python code.\\n\\n```python\\nfrom sympy import symbols, factor\\r\\nx = symbols('x')\\r\\nfactorized = factor(x**2 + 17*x + 42)\\r\\nfactorized\\r\\n```\\n\\nPlease run the Python code above to find the factorized form of the quadratic, and we can proceed to determine the intervals where the inequality holds true.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 24.91333508491516,\n", - " \"trial\": -1\n", - "}\n", - "\n", - "Evaluation dictionary: {\n", - " \"Problem Interpretation\": {\n", - " \"description\": \"Ability to correctly interpret the problem.\",\n", - " \"accepted_values\": [\"completely off\", \"slightly relevant\", \"relevant\", \"mostly accurate\", \"completely accurate\"]\n", - " },\n", - " \"Mathematical Methodology\": {\n", - " \"description\": \"Adequacy of the chosen mathematical or algorithmic methodology for the question\",\n", - " \"accepted_values\": [\"inappropriate\", \"barely adequate\", \"adequate\", \"mostly effective\", \"completely effective\"]\n", - " },\n", - " \"Calculation Correctness\": {\n", - " \"description\": \"Accuracy of calculations made and solutions given\",\n", - " \"accepted_values\": [\"completely incorrect\", \"mostly incorrect\", \"neither\", \"mostly correct\", \"completely correct\"]\n", - " },\n", - " \"Explanation Clarity\": {\n", - " \"description\": \"Clarity and comprehensibility of explanations, including language use and structure\",\n", - " \"accepted_values\": [\"not at all clear\", \"slightly clear\", \"moderately clear\", \"very clear\", \"completely clear\"]\n", - " },\n", - " \"Code Efficiency\": {\n", - " \"description\": \"Quality of code in terms of efficiency and elegance\",\n", - " \"accepted_values\": [\"not at all efficient\", \"slightly efficient\", \"moderately efficient\", \"very efficient\", \"extremely efficient\"]\n", - " },\n", - " \"Code Correctness\": {\n", - " \"description\": \"Correctness of the provided code\",\n", - " \"accepted_values\": [\"completely incorrect\", \"mostly incorrect\", \"partly correct\", \"mostly correct\", \"completely correct\"]\n", - " }\n", + " Task description: Given any question, the system needs to solve the problem as consisely and accurately as possible\n", + " Task successful example: {'problem': 'What is the sum of all the distinct positive two-digit factors of 144?', 'level': 'Level 5', 'type': 'Number Theory', 'solution': 'Prime factorize $144=2^4\\\\cdot3^2$. The sum of the positive two-digit factors of 144 is $2^4+2\\\\cdot3^2+2^2\\\\cdot3+2^2\\\\cdot3^2+2^3\\\\cdot3+2^3\\\\cdot3^2+2^4\\\\cdot3=\\\\boxed{226}.$', 'problem_id': '0', 'response_with_ans': 'To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere\\'s a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\"The sum of all the distinct positive two-digit factors of 144 is:\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.', 'round': 0, 'messages': [{'content': 'What is the sum of all the distinct positive two-digit factors of 144?', 'role': 'user'}, {'content': 'To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere\\'s a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\"The sum of all the distinct positive two-digit factors of 144 is:\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.', 'role': 'assistant'}], 'time': 11.140539407730103, 'trial': -1}\n", + " Task failed example: {'problem': 'Find all $x$ that satisfy the inequality $(2x+10)(x+3)<(3x+9)(x+8)$. Express your answer in interval notation.', 'level': 'Level 5', 'type': 'Algebra', 'solution': 'We have \\\\begin{align*} (2x+10)(x+3)&<(3x+9)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)&<3(x+3)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)-3(x+3)(x+8)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (2x+10-(3x+24))(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (-x-14)(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (x+14)(x+3)&>0.\\n\\\\end{align*} This inequality is satisfied if and only if $(x+14)$ and $(x+3)$ are either both positive or both negative. Both factors are positive for $x>-3$ and both factors are negative for $x<-14$. When $-14 0$ and then find intervals where the inequality holds true.\\n\\nLet's first find the factorized form of the quadratic:\", 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': \"I apologize for the confusion in my previous message. Let's find the factorized form of the quadratic using Python code.\\n\\n```python\\nfrom sympy import symbols, factor\\r\\nx = symbols('x')\\r\\nfactorized = factor(x**2 + 17*x + 42)\\r\\nfactorized\\r\\n```\\n\\nPlease run the Python code above to find the factorized form of the quadratic, and we can proceed to determine the intervals where the inequality holds true.\", 'role': 'assistant'}], 'time': 24.91333508491516, 'trial': -1}\n", + " Evaluation dictionary: [\n", + " {\n", + " \"name\": \"Problem Interpretation\",\n", + " \"description\": \"Ability to correctly interpret the problem.\",\n", + " \"accepted_values\": [\n", + " \"completely off\",\n", + " \"slightly relevant\",\n", + " \"relevant\",\n", + " \"mostly accurate\",\n", + " \"completely accurate\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Mathematical Methodology\",\n", + " \"description\": \"Adequacy of the chosen mathematical or algorithmic methodology for the question\",\n", + " \"accepted_values\": [\n", + " \"inappropriate\",\n", + " \"barely adequate\",\n", + " \"adequate\",\n", + " \"mostly effective\",\n", + " \"completely effective\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Calculation Correctness\",\n", + " \"description\": \"Accuracy of calculations made and solutions given\",\n", + " \"accepted_values\": [\n", + " \"completely incorrect\",\n", + " \"mostly incorrect\",\n", + " \"neither\",\n", + " \"mostly correct\",\n", + " \"completely correct\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Explanation Clarity\",\n", + " \"description\": \"Clarity and comprehensibility of explanations, including language use and structure\",\n", + " \"accepted_values\": [\n", + " \"not at all clear\",\n", + " \"slightly clear\",\n", + " \"moderately clear\",\n", + " \"very clear\",\n", + " \"completely clear\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Code Efficiency\",\n", + " \"description\": \"Quality of code in terms of efficiency and elegance\",\n", + " \"accepted_values\": [\n", + " \"not at all efficient\",\n", + " \"slightly efficient\",\n", + " \"moderately efficient\",\n", + " \"very efficient\",\n", + " \"extremely efficient\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Code Correctness\",\n", + " \"description\": \"Correctness of the provided code\",\n", + " \"accepted_values\": [\n", + " \"completely incorrect\",\n", + " \"mostly incorrect\",\n", + " \"partly correct\",\n", + " \"mostly correct\",\n", + " \"completely correct\"\n", + " ],\n", + " \"sub_criteria\": []\n", " }\n", - "actual test case to evaluate: {\n", - " \"problem\": \"The perfect squares from $1$ through $2500,$ inclusive, are printed in a sequence of digits $1491625\\\\ldots2500.$ How many digits are in the sequence?\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Prealgebra\",\n", - " \"solution\": \"We consider it by four cases:\\n\\n$\\\\bullet$ Case 1: There are $3$ perfect squares that only have $1$ digit, $1^{2},$ $2^{2},$ and $3^{2}.$\\n\\n$\\\\bullet$ Case 2: The smallest perfect square that has $2$ digits is $4^{2},$ and the largest is $9^{2},$ so that's a total of $6$ perfect squares with $2$ digits.\\n\\n$\\\\bullet$ Case 3: The smallest perfect square with $3$ digits is $10^{2},$ and the largest is $31^{2},$ yielding a total of $22.$\\n\\n$\\\\bullet$ Case 4: The smallest perfect square with $4$ digits is $32^{2},$ and the last one that is no greater than $2500$ is $50^{2},$ giving a total of $19.$\\n\\nSo we have a total of $1\\\\times3+2\\\\times6+3\\\\times22+4\\\\times19=\\\\boxed{157}$ digits.\",\n", - " \"problem_id\": \"5\",\n", - " \"response_with_ans\": \"```python\\ndef count_digits(n):\\n return len(str(n))\\n\\nperfect_squares = [i ** 2 for i in range(1, int(2500 ** 0.5) + 1)]\\ntotal_digits = sum(count_digits(square) for square in perfect_squares)\\n\\nprint(total_digits)\\n```\\n\",\n", - " \"round\": 1,\n", - " \"messages\": [\n", - " {\n", - " \"content\": \"The perfect squares from $1$ through $2500,$ inclusive, are printed in a sequence of digits $1491625\\\\ldots2500.$ How many digits are in the sequence?\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"To determine the number of digits in the sequence, we can first find all perfect squares from 1 through 2500, inclusive. Then, we can count the number of digits in each perfect square and sum them up. Let's implement this in Python code:\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"```python\\ndef count_digits(n):\\n return len(str(n))\\n\\nperfect_squares = [i ** 2 for i in range(1, int(2500 ** 0.5) + 1)]\\ntotal_digits = sum(count_digits(square) for square in perfect_squares)\\n\\nprint(total_digits)\\n```\\n\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 9.788081884384155,\n", - " \"trial\": -1\n", - "}\n", + "]actual test case to evaluate: {'problem': 'A $30^\\\\circ$-$60^\\\\circ$-$90^\\\\circ$ triangle is drawn on the exterior of an equilateral triangle so the hypotenuse of the right triangle is one side of the equilateral triangle. If the shorter leg of the right triangle is 6 units, what is the distance between the two vertices that the triangles do not have in common? Express your answer in simplest radical form. [asy]\\ndraw((2,0)--(0,0)--(1,1.732)--(2,1.732)--(2,0)--(1,1.732));\\ndraw((2,1.632)--(1.9,1.632)--(1.9,1.732));\\nlabel(\"$60^\\\\circ$\",(1,1.732),2SE+E);\\nlabel(\"$30^\\\\circ$\",(2,0),5NNW+4N);\\nlabel(\"6\",(1.5,1.732),N);\\n[/asy]', 'level': 'Level 5', 'type': 'Prealgebra', 'solution': 'Multiply the short leg of the right triangle by $\\\\sqrt{3}$ to find that the length of the longer leg is $6\\\\sqrt{3}$ units. Double the short leg of the right triangle to find that the length of the hypotenuse of the right triangle is 12 units. Since the hypotenuse of the right triangle is a side of the equilateral triangle, the side length of the equilateral triangle is also 12 units. By the Pythagorean theorem, the distance between the two vertices that the two triangles do not have in common is $\\\\sqrt{(6\\\\sqrt{3})^2+12^2}=\\\\sqrt{252}=\\\\boxed{6\\\\sqrt{7}}$ units. [asy]\\ndraw((2,0)--(0,0)--(1,sqrt(3))--(2,sqrt(3))--(2,0)--(1,sqrt(3)));\\ndraw((2,sqrt(3)-0.1)--(1.9,sqrt(3)-0.1)--(1.9,sqrt(3)));\\ndraw((0,0)--(2,sqrt(3)));\\nlabel(\"$60^\\\\circ$\",(1,sqrt(3)),2SE+E);\\nlabel(\"$30^\\\\circ$\",(2,0),5NNW+4N);\\nlabel(\"6\",(1.5,sqrt(3)),N);\\nlabel(\"$6\\\\sqrt{3}$\",(2,sqrt(3)/2),E);\\nlabel(\"12\",(1.5,sqrt(3)/2),SW);\\nlabel(\"12\",(1,0),S);\\n[/asy]', 'problem_id': '7', 'response_with_ans': 'We have found the distance between the two vertices that the triangles do not have in common (C and D):\\n\\nx = √(252 + 72√3)\\n\\nThis is the simplest radical form for the required distance. \\n\\nTERMINATE', 'round': 3, 'messages': [{'content': 'A $30^\\\\circ$-$60^\\\\circ$-$90^\\\\circ$ triangle is drawn on the exterior of an equilateral triangle so the hypotenuse of the right triangle is one side of the equilateral triangle. If the shorter leg of the right triangle is 6 units, what is the distance between the two vertices that the triangles do not have in common? Express your answer in simplest radical form. [asy]\\ndraw((2,0)--(0,0)--(1,1.732)--(2,1.732)--(2,0)--(1,1.732));\\ndraw((2,1.632)--(1.9,1.632)--(1.9,1.732));\\nlabel(\"$60^\\\\circ$\",(1,1.732),2SE+E);\\nlabel(\"$30^\\\\circ$\",(2,0),5NNW+4N);\\nlabel(\"6\",(1.5,1.732),N);\\n[/asy]', 'role': 'user'}, {'content': \"Let's denote the vertices of the equilateral triangle as A, B, and C, where A is the right angle of the $30^\\\\circ$-$60^\\\\circ$-$90^\\\\circ$ triangle, B is adjacent to the $30^\\\\circ$ angle, and C is adjacent to the $60^\\\\circ$ angle. Let D be the vertex of the right triangle that is not a part of the equilateral triangle (the $60^\\\\circ$ angle).\\n\\nSince the shorter leg of the right triangle is 6 units (AD) and it is a $30^\\\\circ$-$60^\\\\circ$-$90^\\\\circ$ triangle, we know that:\\n1. The longer leg (BD) has a length equal to the shorter leg times $\\\\sqrt{3}$ ($6\\\\sqrt{3}$ units).\\n2. The hypotenuse (AB) has a length twice the size of the shorter leg (12 units).\\n\\nNow, we are trying to find the distance between vertices C and D. Let's call this distance x.\\n\\nTo calculate x, we can use the law of cosines on triangle $\\\\bigtriangleup BCD$. Here, the angle at vertex B is $180^\\\\circ - 60^\\\\circ = 120^\\\\circ$. We have the lengths of sides BC and BD, so we can calculate x using the law of cosines and then simplify the result.\\n\\nThe law of cosines formula is:\\nx^2 = (BC^2) + (BD^2) - 2(BC)(BD)cos(120^\\\\circ)\", 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': 'First, we need the length of side BC, which is equal to the length of side AB (since it is an equilateral triangle). Thus, BC = 12 units. Now we can use the law of cosines formula to find the value of x^2:\\n\\nx^2 = (12^2) + (6√3)^2 - 2(12)(6√3)cos(120ΒΊ)\\n\\nThe cosine of 120ΒΊ is -1/2, so:\\n\\nx^2 = 144 + 108 - 2(12)(6√3)(-1/2)\\n', 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': \"Let's simplify the expression for x^2:\\n\\nx^2 = 144 + 108 + (12)(6√3)\\n\\nNow we will calculate the result:\\n\\nx^2 = 144 + 108 + 72√3\\n\\nx^2 = 252 + 72√3\\n\\nNow, to find the value of x, we'll take the square root of both sides:\\n\\nx = √(252 + 72√3)\", 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': 'We have found the distance between the two vertices that the triangles do not have in common (C and D):\\n\\nx = √(252 + 72√3)\\n\\nThis is the simplest radical form for the required distance. \\n\\nTERMINATE', 'role': 'assistant'}], 'time': 35.88237428665161, 'trial': -1}\n", "\n", "--------------------------------------------------------------------------------\n", + "\u001b[31m\n", + ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", "\u001b[33mquantifier\u001b[0m (to quantifier_user):\n", "\n", "```json\n", "{\n", " \"Problem Interpretation\": \"completely accurate\",\n", - " \"Mathematical Methodology\": \"mostly effective\",\n", - " \"Calculation Correctness\": \"completely correct\",\n", - " \"Explanation Clarity\": \"very clear\",\n", - " \"Code Efficiency\": \"moderately efficient\",\n", - " \"Code Correctness\": \"completely correct\"\n", + " \"Mathematical Methodology\": \"completely effective\",\n", + " \"Calculation Correctness\": \"mostly correct\",\n", + " \"Explanation Clarity\": \"mostly clear\",\n", + " \"Code Efficiency\": \"N/A\",\n", + " \"Code Correctness\": \"N/A\"\n", "}\n", "```\n", "\n", "--------------------------------------------------------------------------------\n", - "actual label for this case: true\n", "\u001b[33mquantifier_user\u001b[0m (to quantifier):\n", "\n", "Task: Math problem solving.\n", - "Task description: Given any question, the system needs to solve the problem as consisely and accurately as possible\n", - "Task successful example: {\n", - " \"problem\": \"What is the sum of all the distinct positive two-digit factors of 144?\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Number Theory\",\n", - " \"solution\": \"Prime factorize $144=2^4\\\\cdot3^2$. The sum of the positive two-digit factors of 144 is $2^4+2\\\\cdot3^2+2^2\\\\cdot3+2^2\\\\cdot3^2+2^3\\\\cdot3+2^3\\\\cdot3^2+2^4\\\\cdot3=\\\\boxed{226}.$\",\n", - " \"problem_id\": \"0\",\n", - " \"response_with_ans\": \"To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere's a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\\\"The sum of all the distinct positive two-digit factors of 144 is:\\\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.\",\n", - " \"round\": 0,\n", - " \"messages\": [\n", - " {\n", - " \"content\": \"What is the sum of all the distinct positive two-digit factors of 144?\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere's a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\\\"The sum of all the distinct positive two-digit factors of 144 is:\\\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 11.140539407730103,\n", - " \"trial\": -1\n", - "}\n", - "\n", - "Task failed example: {\n", - " \"problem\": \"Find all $x$ that satisfy the inequality $(2x+10)(x+3)<(3x+9)(x+8)$. Express your answer in interval notation.\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Algebra\",\n", - " \"solution\": \"We have \\\\begin{align*} (2x+10)(x+3)&<(3x+9)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)&<3(x+3)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)-3(x+3)(x+8)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (2x+10-(3x+24))(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (-x-14)(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (x+14)(x+3)&>0.\\n\\\\end{align*} This inequality is satisfied if and only if $(x+14)$ and $(x+3)$ are either both positive or both negative. Both factors are positive for $x>-3$ and both factors are negative for $x<-14$. When $-14 0$ and then find intervals where the inequality holds true.\\n\\nLet's first find the factorized form of the quadratic:\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"I apologize for the confusion in my previous message. Let's find the factorized form of the quadratic using Python code.\\n\\n```python\\nfrom sympy import symbols, factor\\r\\nx = symbols('x')\\r\\nfactorized = factor(x**2 + 17*x + 42)\\r\\nfactorized\\r\\n```\\n\\nPlease run the Python code above to find the factorized form of the quadratic, and we can proceed to determine the intervals where the inequality holds true.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 24.91333508491516,\n", - " \"trial\": -1\n", - "}\n", - "\n", - "Evaluation dictionary: {\n", - " \"Problem Interpretation\": {\n", - " \"description\": \"Ability to correctly interpret the problem.\",\n", - " \"accepted_values\": [\"completely off\", \"slightly relevant\", \"relevant\", \"mostly accurate\", \"completely accurate\"]\n", - " },\n", - " \"Mathematical Methodology\": {\n", - " \"description\": \"Adequacy of the chosen mathematical or algorithmic methodology for the question\",\n", - " \"accepted_values\": [\"inappropriate\", \"barely adequate\", \"adequate\", \"mostly effective\", \"completely effective\"]\n", - " },\n", - " \"Calculation Correctness\": {\n", - " \"description\": \"Accuracy of calculations made and solutions given\",\n", - " \"accepted_values\": [\"completely incorrect\", \"mostly incorrect\", \"neither\", \"mostly correct\", \"completely correct\"]\n", - " },\n", - " \"Explanation Clarity\": {\n", - " \"description\": \"Clarity and comprehensibility of explanations, including language use and structure\",\n", - " \"accepted_values\": [\"not at all clear\", \"slightly clear\", \"moderately clear\", \"very clear\", \"completely clear\"]\n", - " },\n", - " \"Code Efficiency\": {\n", - " \"description\": \"Quality of code in terms of efficiency and elegance\",\n", - " \"accepted_values\": [\"not at all efficient\", \"slightly efficient\", \"moderately efficient\", \"very efficient\", \"extremely efficient\"]\n", - " },\n", - " \"Code Correctness\": {\n", - " \"description\": \"Correctness of the provided code\",\n", - " \"accepted_values\": [\"completely incorrect\", \"mostly incorrect\", \"partly correct\", \"mostly correct\", \"completely correct\"]\n", - " }\n", + " Task description: Given any question, the system needs to solve the problem as consisely and accurately as possible\n", + " Task successful example: {'problem': 'What is the sum of all the distinct positive two-digit factors of 144?', 'level': 'Level 5', 'type': 'Number Theory', 'solution': 'Prime factorize $144=2^4\\\\cdot3^2$. The sum of the positive two-digit factors of 144 is $2^4+2\\\\cdot3^2+2^2\\\\cdot3+2^2\\\\cdot3^2+2^3\\\\cdot3+2^3\\\\cdot3^2+2^4\\\\cdot3=\\\\boxed{226}.$', 'problem_id': '0', 'response_with_ans': 'To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere\\'s a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\"The sum of all the distinct positive two-digit factors of 144 is:\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.', 'round': 0, 'messages': [{'content': 'What is the sum of all the distinct positive two-digit factors of 144?', 'role': 'user'}, {'content': 'To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere\\'s a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\"The sum of all the distinct positive two-digit factors of 144 is:\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.', 'role': 'assistant'}], 'time': 11.140539407730103, 'trial': -1}\n", + " Task failed example: {'problem': 'Find all $x$ that satisfy the inequality $(2x+10)(x+3)<(3x+9)(x+8)$. Express your answer in interval notation.', 'level': 'Level 5', 'type': 'Algebra', 'solution': 'We have \\\\begin{align*} (2x+10)(x+3)&<(3x+9)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)&<3(x+3)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)-3(x+3)(x+8)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (2x+10-(3x+24))(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (-x-14)(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (x+14)(x+3)&>0.\\n\\\\end{align*} This inequality is satisfied if and only if $(x+14)$ and $(x+3)$ are either both positive or both negative. Both factors are positive for $x>-3$ and both factors are negative for $x<-14$. When $-14 0$ and then find intervals where the inequality holds true.\\n\\nLet's first find the factorized form of the quadratic:\", 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': \"I apologize for the confusion in my previous message. Let's find the factorized form of the quadratic using Python code.\\n\\n```python\\nfrom sympy import symbols, factor\\r\\nx = symbols('x')\\r\\nfactorized = factor(x**2 + 17*x + 42)\\r\\nfactorized\\r\\n```\\n\\nPlease run the Python code above to find the factorized form of the quadratic, and we can proceed to determine the intervals where the inequality holds true.\", 'role': 'assistant'}], 'time': 24.91333508491516, 'trial': -1}\n", + " Evaluation dictionary: [\n", + " {\n", + " \"name\": \"Problem Interpretation\",\n", + " \"description\": \"Ability to correctly interpret the problem.\",\n", + " \"accepted_values\": [\n", + " \"completely off\",\n", + " \"slightly relevant\",\n", + " \"relevant\",\n", + " \"mostly accurate\",\n", + " \"completely accurate\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Mathematical Methodology\",\n", + " \"description\": \"Adequacy of the chosen mathematical or algorithmic methodology for the question\",\n", + " \"accepted_values\": [\n", + " \"inappropriate\",\n", + " \"barely adequate\",\n", + " \"adequate\",\n", + " \"mostly effective\",\n", + " \"completely effective\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Calculation Correctness\",\n", + " \"description\": \"Accuracy of calculations made and solutions given\",\n", + " \"accepted_values\": [\n", + " \"completely incorrect\",\n", + " \"mostly incorrect\",\n", + " \"neither\",\n", + " \"mostly correct\",\n", + " \"completely correct\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Explanation Clarity\",\n", + " \"description\": \"Clarity and comprehensibility of explanations, including language use and structure\",\n", + " \"accepted_values\": [\n", + " \"not at all clear\",\n", + " \"slightly clear\",\n", + " \"moderately clear\",\n", + " \"very clear\",\n", + " \"completely clear\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Code Efficiency\",\n", + " \"description\": \"Quality of code in terms of efficiency and elegance\",\n", + " \"accepted_values\": [\n", + " \"not at all efficient\",\n", + " \"slightly efficient\",\n", + " \"moderately efficient\",\n", + " \"very efficient\",\n", + " \"extremely efficient\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Code Correctness\",\n", + " \"description\": \"Correctness of the provided code\",\n", + " \"accepted_values\": [\n", + " \"completely incorrect\",\n", + " \"mostly incorrect\",\n", + " \"partly correct\",\n", + " \"mostly correct\",\n", + " \"completely correct\"\n", + " ],\n", + " \"sub_criteria\": []\n", " }\n", - "actual test case to evaluate: {\n", - " \"problem\": \"All 50 states as well as the District of Columbia and Puerto Rico, have distinct two-letter postal abbreviations. If a two-letter sequence of letters (such as CO or EE) is chosen at random, what is the probability that it is a postal abbreviation for one of the 50 states, the District of Columbia, or Puerto Rico? Express your answer as a common fraction.\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Prealgebra\",\n", - " \"solution\": \"There are $26\\\\cdot 26$ possible two-letter sequences of letters, since we have 26 choices for the first and then 26 choices for the second. But only 52 of these possibilities are valid, so our answer is $\\\\frac{52}{26\\\\cdot 26} =\\\\boxed{ \\\\frac{1}{13}}$.\",\n", - " \"problem_id\": \"8\",\n", - " \"response_with_ans\": \"We'll use a reliable source like USPS to fetch the postal abbreviations, and then calculate the probability.\\n\\n```python\\nimport requests\\nfrom bs4 import BeautifulSoup\\nimport fractions\\n\\nurl = 'https://pe.usps.com/text/pub28/28apb.htm'\\nresponse = requests.get(url)\\nsoup = BeautifulSoup(response.text, 'html.parser')\\n\\n# Find the table containing the state info\\nstate_table = soup.find_all('table')[0]\\n\\n# Extract state abbreviations from the table\\nstate_abbreviations = [row.find_all('td')[-1].text.strip() for row in state_table.find_all('tr')[3:-2]]\\n\\n# Add District of Columbia and Puerto Rico.\\nstate_abbreviations.extend([\\\"DC\\\", \\\"PR\\\"])\\n\\nnum_valid_abbreviations = len(state_abbreviations)\\ntotal_possible_combinations = 26 * 26\\nprobability = fractions.Fraction(num_valid_abbreviations, total_possible_combinations)\\n\\nprint(f\\\"Valid postal abbreviations: {state_abbreviations}\\\")\\nprint(f\\\"Total valid postal abbreviations: {num_valid_abbreviations}\\\")\\nprint(f\\\"Total possible two-letter sequences: {total_possible_combinations}\\\")\\nprint(f\\\"Probability: {probability}\\\")\\n```\\n\\nPlease run the above code, and the output will show you the list of valid postal abbreviations, the total number of valid abbreviations, the total possible two-letter sequences, and the probability.\",\n", - " \"round\": 1,\n", - " \"messages\": [\n", - " {\n", - " \"content\": \"All 50 states as well as the District of Columbia and Puerto Rico, have distinct two-letter postal abbreviations. If a two-letter sequence of letters (such as CO or EE) is chosen at random, what is the probability that it is a postal abbreviation for one of the 50 states, the District of Columbia, or Puerto Rico? Express your answer as a common fraction.\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"To calculate the probability, we first need to know the total number of possible two-letter sequences and the number of valid postal abbreviations.\\n\\nTo get the total number of possible two-letter sequences, we'll calculate all possible combinations using 26 letters of the alphabet. There are 26 options for choosing the first letter and 26 options for choosing the second letter, so the possible sequences are 26 * 26.\\n\\nNext, let's find the valid postal abbreviations for all 50 states, District of Columbia, and Puerto Rico. To do this, we can create code to download the postal abbreviations.\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"We'll use a reliable source like USPS to fetch the postal abbreviations, and then calculate the probability.\\n\\n```python\\nimport requests\\nfrom bs4 import BeautifulSoup\\nimport fractions\\n\\nurl = 'https://pe.usps.com/text/pub28/28apb.htm'\\nresponse = requests.get(url)\\nsoup = BeautifulSoup(response.text, 'html.parser')\\n\\n# Find the table containing the state info\\nstate_table = soup.find_all('table')[0]\\n\\n# Extract state abbreviations from the table\\nstate_abbreviations = [row.find_all('td')[-1].text.strip() for row in state_table.find_all('tr')[3:-2]]\\n\\n# Add District of Columbia and Puerto Rico.\\nstate_abbreviations.extend([\\\"DC\\\", \\\"PR\\\"])\\n\\nnum_valid_abbreviations = len(state_abbreviations)\\ntotal_possible_combinations = 26 * 26\\nprobability = fractions.Fraction(num_valid_abbreviations, total_possible_combinations)\\n\\nprint(f\\\"Valid postal abbreviations: {state_abbreviations}\\\")\\nprint(f\\\"Total valid postal abbreviations: {num_valid_abbreviations}\\\")\\nprint(f\\\"Total possible two-letter sequences: {total_possible_combinations}\\\")\\nprint(f\\\"Probability: {probability}\\\")\\n```\\n\\nPlease run the above code, and the output will show you the list of valid postal abbreviations, the total number of valid abbreviations, the total possible two-letter sequences, and the probability.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 27.01692771911621,\n", - " \"trial\": -1\n", - "}\n", + "]actual test case to evaluate: {'problem': 'The perfect squares from $1$ through $2500,$ inclusive, are printed in a sequence of digits $1491625\\\\ldots2500.$ How many digits are in the sequence?', 'level': 'Level 5', 'type': 'Prealgebra', 'solution': \"We consider it by four cases:\\n\\n$\\\\bullet$ Case 1: There are $3$ perfect squares that only have $1$ digit, $1^{2},$ $2^{2},$ and $3^{2}.$\\n\\n$\\\\bullet$ Case 2: The smallest perfect square that has $2$ digits is $4^{2},$ and the largest is $9^{2},$ so that's a total of $6$ perfect squares with $2$ digits.\\n\\n$\\\\bullet$ Case 3: The smallest perfect square with $3$ digits is $10^{2},$ and the largest is $31^{2},$ yielding a total of $22.$\\n\\n$\\\\bullet$ Case 4: The smallest perfect square with $4$ digits is $32^{2},$ and the last one that is no greater than $2500$ is $50^{2},$ giving a total of $19.$\\n\\nSo we have a total of $1\\\\times3+2\\\\times6+3\\\\times22+4\\\\times19=\\\\boxed{157}$ digits.\", 'problem_id': '5', 'response_with_ans': '```python\\ndef count_digits(n):\\n return len(str(n))\\n\\nperfect_squares = [i ** 2 for i in range(1, int(2500 ** 0.5) + 1)]\\ntotal_digits = sum(count_digits(square) for square in perfect_squares)\\n\\nprint(total_digits)\\n```\\n', 'round': 1, 'messages': [{'content': 'The perfect squares from $1$ through $2500,$ inclusive, are printed in a sequence of digits $1491625\\\\ldots2500.$ How many digits are in the sequence?', 'role': 'user'}, {'content': \"To determine the number of digits in the sequence, we can first find all perfect squares from 1 through 2500, inclusive. Then, we can count the number of digits in each perfect square and sum them up. Let's implement this in Python code:\", 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': '```python\\ndef count_digits(n):\\n return len(str(n))\\n\\nperfect_squares = [i ** 2 for i in range(1, int(2500 ** 0.5) + 1)]\\ntotal_digits = sum(count_digits(square) for square in perfect_squares)\\n\\nprint(total_digits)\\n```\\n', 'role': 'assistant'}], 'time': 9.788081884384155, 'trial': -1}\n", "\n", "--------------------------------------------------------------------------------\n", + "\u001b[31m\n", + ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", "\u001b[33mquantifier\u001b[0m (to quantifier_user):\n", "\n", + "```json\n", "{\n", " \"Problem Interpretation\": \"completely accurate\",\n", " \"Mathematical Methodology\": \"completely effective\",\n", " \"Calculation Correctness\": \"completely correct\",\n", " \"Explanation Clarity\": \"very clear\",\n", " \"Code Efficiency\": \"moderately efficient\",\n", - " \"Code Correctness\": \"mostly correct\"\n", + " \"Code Correctness\": \"completely correct\"\n", "}\n", + "```\n", "\n", "--------------------------------------------------------------------------------\n", - "actual label for this case: true\n", "\u001b[33mquantifier_user\u001b[0m (to quantifier):\n", "\n", "Task: Math problem solving.\n", - "Task description: Given any question, the system needs to solve the problem as consisely and accurately as possible\n", - "Task successful example: {\n", - " \"problem\": \"What is the sum of all the distinct positive two-digit factors of 144?\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Number Theory\",\n", - " \"solution\": \"Prime factorize $144=2^4\\\\cdot3^2$. The sum of the positive two-digit factors of 144 is $2^4+2\\\\cdot3^2+2^2\\\\cdot3+2^2\\\\cdot3^2+2^3\\\\cdot3+2^3\\\\cdot3^2+2^4\\\\cdot3=\\\\boxed{226}.$\",\n", - " \"problem_id\": \"0\",\n", - " \"response_with_ans\": \"To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere's a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\\\"The sum of all the distinct positive two-digit factors of 144 is:\\\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.\",\n", - " \"round\": 0,\n", - " \"messages\": [\n", - " {\n", - " \"content\": \"What is the sum of all the distinct positive two-digit factors of 144?\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere's a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\\\"The sum of all the distinct positive two-digit factors of 144 is:\\\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 11.140539407730103,\n", - " \"trial\": -1\n", - "}\n", - "\n", - "Task failed example: {\n", - " \"problem\": \"Find all $x$ that satisfy the inequality $(2x+10)(x+3)<(3x+9)(x+8)$. Express your answer in interval notation.\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Algebra\",\n", - " \"solution\": \"We have \\\\begin{align*} (2x+10)(x+3)&<(3x+9)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)&<3(x+3)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)-3(x+3)(x+8)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (2x+10-(3x+24))(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (-x-14)(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (x+14)(x+3)&>0.\\n\\\\end{align*} This inequality is satisfied if and only if $(x+14)$ and $(x+3)$ are either both positive or both negative. Both factors are positive for $x>-3$ and both factors are negative for $x<-14$. When $-14 0$ and then find intervals where the inequality holds true.\\n\\nLet's first find the factorized form of the quadratic:\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"I apologize for the confusion in my previous message. Let's find the factorized form of the quadratic using Python code.\\n\\n```python\\nfrom sympy import symbols, factor\\r\\nx = symbols('x')\\r\\nfactorized = factor(x**2 + 17*x + 42)\\r\\nfactorized\\r\\n```\\n\\nPlease run the Python code above to find the factorized form of the quadratic, and we can proceed to determine the intervals where the inequality holds true.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 24.91333508491516,\n", - " \"trial\": -1\n", - "}\n", - "\n", - "Evaluation dictionary: {\n", - " \"Problem Interpretation\": {\n", - " \"description\": \"Ability to correctly interpret the problem.\",\n", - " \"accepted_values\": [\"completely off\", \"slightly relevant\", \"relevant\", \"mostly accurate\", \"completely accurate\"]\n", - " },\n", - " \"Mathematical Methodology\": {\n", - " \"description\": \"Adequacy of the chosen mathematical or algorithmic methodology for the question\",\n", - " \"accepted_values\": [\"inappropriate\", \"barely adequate\", \"adequate\", \"mostly effective\", \"completely effective\"]\n", - " },\n", - " \"Calculation Correctness\": {\n", - " \"description\": \"Accuracy of calculations made and solutions given\",\n", - " \"accepted_values\": [\"completely incorrect\", \"mostly incorrect\", \"neither\", \"mostly correct\", \"completely correct\"]\n", - " },\n", - " \"Explanation Clarity\": {\n", - " \"description\": \"Clarity and comprehensibility of explanations, including language use and structure\",\n", - " \"accepted_values\": [\"not at all clear\", \"slightly clear\", \"moderately clear\", \"very clear\", \"completely clear\"]\n", - " },\n", - " \"Code Efficiency\": {\n", - " \"description\": \"Quality of code in terms of efficiency and elegance\",\n", - " \"accepted_values\": [\"not at all efficient\", \"slightly efficient\", \"moderately efficient\", \"very efficient\", \"extremely efficient\"]\n", - " },\n", - " \"Code Correctness\": {\n", - " \"description\": \"Correctness of the provided code\",\n", - " \"accepted_values\": [\"completely incorrect\", \"mostly incorrect\", \"partly correct\", \"mostly correct\", \"completely correct\"]\n", - " }\n", + " Task description: Given any question, the system needs to solve the problem as consisely and accurately as possible\n", + " Task successful example: {'problem': 'What is the sum of all the distinct positive two-digit factors of 144?', 'level': 'Level 5', 'type': 'Number Theory', 'solution': 'Prime factorize $144=2^4\\\\cdot3^2$. The sum of the positive two-digit factors of 144 is $2^4+2\\\\cdot3^2+2^2\\\\cdot3+2^2\\\\cdot3^2+2^3\\\\cdot3+2^3\\\\cdot3^2+2^4\\\\cdot3=\\\\boxed{226}.$', 'problem_id': '0', 'response_with_ans': 'To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere\\'s a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\"The sum of all the distinct positive two-digit factors of 144 is:\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.', 'round': 0, 'messages': [{'content': 'What is the sum of all the distinct positive two-digit factors of 144?', 'role': 'user'}, {'content': 'To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere\\'s a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\"The sum of all the distinct positive two-digit factors of 144 is:\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.', 'role': 'assistant'}], 'time': 11.140539407730103, 'trial': -1}\n", + " Task failed example: {'problem': 'Find all $x$ that satisfy the inequality $(2x+10)(x+3)<(3x+9)(x+8)$. Express your answer in interval notation.', 'level': 'Level 5', 'type': 'Algebra', 'solution': 'We have \\\\begin{align*} (2x+10)(x+3)&<(3x+9)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)&<3(x+3)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)-3(x+3)(x+8)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (2x+10-(3x+24))(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (-x-14)(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (x+14)(x+3)&>0.\\n\\\\end{align*} This inequality is satisfied if and only if $(x+14)$ and $(x+3)$ are either both positive or both negative. Both factors are positive for $x>-3$ and both factors are negative for $x<-14$. When $-14 0$ and then find intervals where the inequality holds true.\\n\\nLet's first find the factorized form of the quadratic:\", 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': \"I apologize for the confusion in my previous message. Let's find the factorized form of the quadratic using Python code.\\n\\n```python\\nfrom sympy import symbols, factor\\r\\nx = symbols('x')\\r\\nfactorized = factor(x**2 + 17*x + 42)\\r\\nfactorized\\r\\n```\\n\\nPlease run the Python code above to find the factorized form of the quadratic, and we can proceed to determine the intervals where the inequality holds true.\", 'role': 'assistant'}], 'time': 24.91333508491516, 'trial': -1}\n", + " Evaluation dictionary: [\n", + " {\n", + " \"name\": \"Problem Interpretation\",\n", + " \"description\": \"Ability to correctly interpret the problem.\",\n", + " \"accepted_values\": [\n", + " \"completely off\",\n", + " \"slightly relevant\",\n", + " \"relevant\",\n", + " \"mostly accurate\",\n", + " \"completely accurate\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Mathematical Methodology\",\n", + " \"description\": \"Adequacy of the chosen mathematical or algorithmic methodology for the question\",\n", + " \"accepted_values\": [\n", + " \"inappropriate\",\n", + " \"barely adequate\",\n", + " \"adequate\",\n", + " \"mostly effective\",\n", + " \"completely effective\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Calculation Correctness\",\n", + " \"description\": \"Accuracy of calculations made and solutions given\",\n", + " \"accepted_values\": [\n", + " \"completely incorrect\",\n", + " \"mostly incorrect\",\n", + " \"neither\",\n", + " \"mostly correct\",\n", + " \"completely correct\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Explanation Clarity\",\n", + " \"description\": \"Clarity and comprehensibility of explanations, including language use and structure\",\n", + " \"accepted_values\": [\n", + " \"not at all clear\",\n", + " \"slightly clear\",\n", + " \"moderately clear\",\n", + " \"very clear\",\n", + " \"completely clear\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Code Efficiency\",\n", + " \"description\": \"Quality of code in terms of efficiency and elegance\",\n", + " \"accepted_values\": [\n", + " \"not at all efficient\",\n", + " \"slightly efficient\",\n", + " \"moderately efficient\",\n", + " \"very efficient\",\n", + " \"extremely efficient\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Code Correctness\",\n", + " \"description\": \"Correctness of the provided code\",\n", + " \"accepted_values\": [\n", + " \"completely incorrect\",\n", + " \"mostly incorrect\",\n", + " \"partly correct\",\n", + " \"mostly correct\",\n", + " \"completely correct\"\n", + " ],\n", + " \"sub_criteria\": []\n", " }\n", - "actual test case to evaluate: {\n", - " \"problem\": \"What is the sum of the lengths, in centimeters, of the two legs of a 30-60-90 right triangle, if the length of the hypotenuse is $2\\\\sqrt{6}$ centimeters?\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Prealgebra\",\n", - " \"solution\": \"We know that the ratio of the lengths of the sides of a 30-60-90 triangle is $1:\\\\sqrt{3}:2$. We know that the length of the hypotenuse is $2\\\\sqrt{6}$ and the ratio of the length shortest leg to that of the hypotenuse is $1:2$. Therefore, the length of the shorter leg is $\\\\sqrt{6}$. Since the ratio of the shorter leg to the longer leg is $1:\\\\sqrt{3}$, the length of the longer leg is $\\\\sqrt{6} \\\\cdot \\\\sqrt{3} = 3\\\\sqrt{2}$. The sum of the lengths of these two legs is $\\\\boxed{\\\\sqrt{6} + 3\\\\sqrt{2}}$ centimeters.\",\n", - " \"problem_id\": \"17\",\n", - " \"response_with_ans\": \"Let's solve for x and find the lengths of the legs.\\n\\n```python\\nimport math\\n\\nhypotenuse = 2 * math.sqrt(6)\\nx = hypotenuse / 2\\n\\nleg_1 = x\\nleg_2 = x * math.sqrt(3)\\nsum_of_legs = leg_1 + leg_2\\n\\nleg_1, leg_2, sum_of_legs\\n```\\n\\nNow, execute the above code to find the sum of the lengths of the two legs.\",\n", - " \"round\": 1,\n", - " \"messages\": [\n", - " {\n", - " \"content\": \"What is the sum of the lengths, in centimeters, of the two legs of a 30-60-90 right triangle, if the length of the hypotenuse is $2\\\\sqrt{6}$ centimeters?\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"To find the sum of the lengths of the two legs, let's first analyze a 30-60-90 right triangle. In a 30-60-90 triangle, the ratios of the side lengths are:\\n\\n1. Opposite the 30-degree angle: $x$\\n2. Opposite the 60-degree angle: $x\\\\sqrt{3}$\\n3. Hypotenuse (opposite the 90-degree angle): $2x$\\n\\nNow, we are given that the hypotenuse is $2\\\\sqrt{6}$ centimeters. We can set up the following equation and solve for $x$:\\n\\n$2x = 2\\\\sqrt{6}$\\n\\nNext, we will find the lengths of the legs and sum them up.\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"Let's solve for x and find the lengths of the legs.\\n\\n```python\\nimport math\\n\\nhypotenuse = 2 * math.sqrt(6)\\nx = hypotenuse / 2\\n\\nleg_1 = x\\nleg_2 = x * math.sqrt(3)\\nsum_of_legs = leg_1 + leg_2\\n\\nleg_1, leg_2, sum_of_legs\\n```\\n\\nNow, execute the above code to find the sum of the lengths of the two legs.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 18.742590188980103,\n", - " \"trial\": -1\n", - "}\n", + "]actual test case to evaluate: {'problem': 'In isosceles right triangle $ABC$, point $D$ is on hypotenuse $\\\\overline{BC}$ such that $\\\\overline{AD}$ is an altitude of $\\\\triangle ABC$ and $DC = 5$. What is the area of triangle $ABC$?', 'level': 'Level 5', 'type': 'Prealgebra', 'solution': 'In isosceles right triangle $\\\\triangle ABC$ below, $\\\\overline{AD}$ is the altitude to the hypotenuse.\\n\\n[asy]\\nimport olympiad;\\nunitsize(0.8inch);\\npair A,B,C,D;\\nA = (0,1);\\nB= (1,0);\\nC = -B;\\nD = (0,0);\\ndraw(A--B--C--A,linewidth(1));\\ndraw(A--D,linewidth(0.8));\\ndraw(rightanglemark(C,A,B,s=4));\\ndraw(rightanglemark(C,D,A,s=4));\\nlabel(\"$A$\",A,N);\\nlabel(\"$B$\",B,S);\\nlabel(\"$C$\",C,S);\\nlabel(\"$D$\",D,S);\\n[/asy]\\n\\nBecause $\\\\triangle ABC$ is an isosceles right triangle, $\\\\angle ABC = 45^\\\\circ$. Since $\\\\angle ADB = 90^\\\\circ$, we know that $\\\\angle DAB = 45^\\\\circ$, so $\\\\triangle ABD$ is also a 45-45-90 triangle. Similarly, $\\\\triangle ACD$ is a 45-45-90 triangle. Therefore, $DA=DB = DC = 5$, so $BC = BD+DC = 10$, and \\\\[[ABC] = \\\\frac{(AD)(BC)}{2} = \\\\frac{(5)({10})}{2} = \\\\boxed{25}.\\\\]', 'problem_id': '13', 'response_with_ans': '```python\\nfrom sympy import Eq, solve, symbols\\r\\n\\r\\n# Step 1: Find the length of side AC using the Pythagorean theorem\\r\\na, b, c = symbols(\"a b c\")\\r\\nPythagorean = Eq(a**2 + b**2, c**2)\\r\\n\\r\\n# We know BD = 5 and it\\'s an isosceles right triangle so:\\r\\nBD = 5\\r\\nBC = BD * 2\\r\\nside_A = BD # Side A is equal to BD\\r\\n\\r\\nisosceles_triangle = Pythagorean.subs({a: side_A, b: side_A, c: BC}) # Substitute values into the equation\\r\\nlength_AC = solve(isosceles_triangle, side_A)[0] # Solve for side_AC\\r\\n\\r\\n# Step 2: Find the length of side AD\\r\\nlength_AD = (BD * length_AC) / BC # Using the proportion\\r\\n\\r\\n# Step 3: Find the area of triangle ABC\\r\\narea_triangle_ABC = 0.5 * (length_AD * length_AC)\\r\\n\\r\\nlength_AC, length_AD, area_triangle_ABC\\r\\n```', 'round': 1, 'messages': [{'content': 'In isosceles right triangle $ABC$, point $D$ is on hypotenuse $\\\\overline{BC}$ such that $\\\\overline{AD}$ is an altitude of $\\\\triangle ABC$ and $DC = 5$. What is the area of triangle $ABC$?', 'role': 'user'}, {'content': \"Let's first analyze the problem and draw a plan to solve it.\\n\\nPlan:\\n1. Use the Pythagorean theorem to find the length of side AC.\\n2. Since right triangle ADC is similar to triangle ABC, we can set up a proportion to find the length of side AD.\\n3. Use the area formula of a triangle to find the area of triangle ABC.\\n\\nNow, let's execute the plan using code to calculate side AC, then side AD, and finally the area of triangle ABC.\\n\\n\", 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': '```python\\nfrom sympy import Eq, solve, symbols\\r\\n\\r\\n# Step 1: Find the length of side AC using the Pythagorean theorem\\r\\na, b, c = symbols(\"a b c\")\\r\\nPythagorean = Eq(a**2 + b**2, c**2)\\r\\n\\r\\n# We know BD = 5 and it\\'s an isosceles right triangle so:\\r\\nBD = 5\\r\\nBC = BD * 2\\r\\nside_A = BD # Side A is equal to BD\\r\\n\\r\\nisosceles_triangle = Pythagorean.subs({a: side_A, b: side_A, c: BC}) # Substitute values into the equation\\r\\nlength_AC = solve(isosceles_triangle, side_A)[0] # Solve for side_AC\\r\\n\\r\\n# Step 2: Find the length of side AD\\r\\nlength_AD = (BD * length_AC) / BC # Using the proportion\\r\\n\\r\\n# Step 3: Find the area of triangle ABC\\r\\narea_triangle_ABC = 0.5 * (length_AD * length_AC)\\r\\n\\r\\nlength_AC, length_AD, area_triangle_ABC\\r\\n```', 'role': 'assistant'}], 'time': 22.85700249671936, 'trial': -1}\n", "\n", "--------------------------------------------------------------------------------\n", + "\u001b[31m\n", + ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", "\u001b[33mquantifier\u001b[0m (to quantifier_user):\n", "\n", + "```json\n", "{\n", " \"Problem Interpretation\": \"completely accurate\",\n", - " \"Mathematical Methodology\": \"completely effective\",\n", - " \"Calculation Correctness\": \"completely correct\",\n", + " \"Mathematical Methodology\": \"mostly effective\",\n", + " \"Calculation Correctness\": \"mostly correct\",\n", " \"Explanation Clarity\": \"very clear\",\n", " \"Code Efficiency\": \"moderately efficient\",\n", - " \"Code Correctness\": \"completely correct\"\n", + " \"Code Correctness\": \"mostly correct\"\n", "}\n", + "```\n", "\n", "--------------------------------------------------------------------------------\n", - "actual label for this case: true\n", "\u001b[33mquantifier_user\u001b[0m (to quantifier):\n", "\n", "Task: Math problem solving.\n", - "Task description: Given any question, the system needs to solve the problem as consisely and accurately as possible\n", - "Task successful example: {\n", - " \"problem\": \"What is the sum of all the distinct positive two-digit factors of 144?\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Number Theory\",\n", - " \"solution\": \"Prime factorize $144=2^4\\\\cdot3^2$. The sum of the positive two-digit factors of 144 is $2^4+2\\\\cdot3^2+2^2\\\\cdot3+2^2\\\\cdot3^2+2^3\\\\cdot3+2^3\\\\cdot3^2+2^4\\\\cdot3=\\\\boxed{226}.$\",\n", - " \"problem_id\": \"0\",\n", - " \"response_with_ans\": \"To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere's a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\\\"The sum of all the distinct positive two-digit factors of 144 is:\\\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.\",\n", - " \"round\": 0,\n", - " \"messages\": [\n", - " {\n", - " \"content\": \"What is the sum of all the distinct positive two-digit factors of 144?\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere's a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\\\"The sum of all the distinct positive two-digit factors of 144 is:\\\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 11.140539407730103,\n", - " \"trial\": -1\n", - "}\n", - "\n", - "Task failed example: {\n", - " \"problem\": \"Find all $x$ that satisfy the inequality $(2x+10)(x+3)<(3x+9)(x+8)$. Express your answer in interval notation.\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Algebra\",\n", - " \"solution\": \"We have \\\\begin{align*} (2x+10)(x+3)&<(3x+9)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)&<3(x+3)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)-3(x+3)(x+8)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (2x+10-(3x+24))(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (-x-14)(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (x+14)(x+3)&>0.\\n\\\\end{align*} This inequality is satisfied if and only if $(x+14)$ and $(x+3)$ are either both positive or both negative. Both factors are positive for $x>-3$ and both factors are negative for $x<-14$. When $-14 0$ and then find intervals where the inequality holds true.\\n\\nLet's first find the factorized form of the quadratic:\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"I apologize for the confusion in my previous message. Let's find the factorized form of the quadratic using Python code.\\n\\n```python\\nfrom sympy import symbols, factor\\r\\nx = symbols('x')\\r\\nfactorized = factor(x**2 + 17*x + 42)\\r\\nfactorized\\r\\n```\\n\\nPlease run the Python code above to find the factorized form of the quadratic, and we can proceed to determine the intervals where the inequality holds true.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 24.91333508491516,\n", - " \"trial\": -1\n", - "}\n", - "\n", - "Evaluation dictionary: {\n", - " \"Problem Interpretation\": {\n", - " \"description\": \"Ability to correctly interpret the problem.\",\n", - " \"accepted_values\": [\"completely off\", \"slightly relevant\", \"relevant\", \"mostly accurate\", \"completely accurate\"]\n", - " },\n", - " \"Mathematical Methodology\": {\n", - " \"description\": \"Adequacy of the chosen mathematical or algorithmic methodology for the question\",\n", - " \"accepted_values\": [\"inappropriate\", \"barely adequate\", \"adequate\", \"mostly effective\", \"completely effective\"]\n", - " },\n", - " \"Calculation Correctness\": {\n", - " \"description\": \"Accuracy of calculations made and solutions given\",\n", - " \"accepted_values\": [\"completely incorrect\", \"mostly incorrect\", \"neither\", \"mostly correct\", \"completely correct\"]\n", - " },\n", - " \"Explanation Clarity\": {\n", - " \"description\": \"Clarity and comprehensibility of explanations, including language use and structure\",\n", - " \"accepted_values\": [\"not at all clear\", \"slightly clear\", \"moderately clear\", \"very clear\", \"completely clear\"]\n", - " },\n", - " \"Code Efficiency\": {\n", - " \"description\": \"Quality of code in terms of efficiency and elegance\",\n", - " \"accepted_values\": [\"not at all efficient\", \"slightly efficient\", \"moderately efficient\", \"very efficient\", \"extremely efficient\"]\n", - " },\n", - " \"Code Correctness\": {\n", - " \"description\": \"Correctness of the provided code\",\n", - " \"accepted_values\": [\"completely incorrect\", \"mostly incorrect\", \"partly correct\", \"mostly correct\", \"completely correct\"]\n", - " }\n", + " Task description: Given any question, the system needs to solve the problem as consisely and accurately as possible\n", + " Task successful example: {'problem': 'What is the sum of all the distinct positive two-digit factors of 144?', 'level': 'Level 5', 'type': 'Number Theory', 'solution': 'Prime factorize $144=2^4\\\\cdot3^2$. The sum of the positive two-digit factors of 144 is $2^4+2\\\\cdot3^2+2^2\\\\cdot3+2^2\\\\cdot3^2+2^3\\\\cdot3+2^3\\\\cdot3^2+2^4\\\\cdot3=\\\\boxed{226}.$', 'problem_id': '0', 'response_with_ans': 'To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere\\'s a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\"The sum of all the distinct positive two-digit factors of 144 is:\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.', 'round': 0, 'messages': [{'content': 'What is the sum of all the distinct positive two-digit factors of 144?', 'role': 'user'}, {'content': 'To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere\\'s a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\"The sum of all the distinct positive two-digit factors of 144 is:\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.', 'role': 'assistant'}], 'time': 11.140539407730103, 'trial': -1}\n", + " Task failed example: {'problem': 'Find all $x$ that satisfy the inequality $(2x+10)(x+3)<(3x+9)(x+8)$. Express your answer in interval notation.', 'level': 'Level 5', 'type': 'Algebra', 'solution': 'We have \\\\begin{align*} (2x+10)(x+3)&<(3x+9)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)&<3(x+3)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)-3(x+3)(x+8)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (2x+10-(3x+24))(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (-x-14)(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (x+14)(x+3)&>0.\\n\\\\end{align*} This inequality is satisfied if and only if $(x+14)$ and $(x+3)$ are either both positive or both negative. Both factors are positive for $x>-3$ and both factors are negative for $x<-14$. When $-14 0$ and then find intervals where the inequality holds true.\\n\\nLet's first find the factorized form of the quadratic:\", 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': \"I apologize for the confusion in my previous message. Let's find the factorized form of the quadratic using Python code.\\n\\n```python\\nfrom sympy import symbols, factor\\r\\nx = symbols('x')\\r\\nfactorized = factor(x**2 + 17*x + 42)\\r\\nfactorized\\r\\n```\\n\\nPlease run the Python code above to find the factorized form of the quadratic, and we can proceed to determine the intervals where the inequality holds true.\", 'role': 'assistant'}], 'time': 24.91333508491516, 'trial': -1}\n", + " Evaluation dictionary: [\n", + " {\n", + " \"name\": \"Problem Interpretation\",\n", + " \"description\": \"Ability to correctly interpret the problem.\",\n", + " \"accepted_values\": [\n", + " \"completely off\",\n", + " \"slightly relevant\",\n", + " \"relevant\",\n", + " \"mostly accurate\",\n", + " \"completely accurate\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Mathematical Methodology\",\n", + " \"description\": \"Adequacy of the chosen mathematical or algorithmic methodology for the question\",\n", + " \"accepted_values\": [\n", + " \"inappropriate\",\n", + " \"barely adequate\",\n", + " \"adequate\",\n", + " \"mostly effective\",\n", + " \"completely effective\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Calculation Correctness\",\n", + " \"description\": \"Accuracy of calculations made and solutions given\",\n", + " \"accepted_values\": [\n", + " \"completely incorrect\",\n", + " \"mostly incorrect\",\n", + " \"neither\",\n", + " \"mostly correct\",\n", + " \"completely correct\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Explanation Clarity\",\n", + " \"description\": \"Clarity and comprehensibility of explanations, including language use and structure\",\n", + " \"accepted_values\": [\n", + " \"not at all clear\",\n", + " \"slightly clear\",\n", + " \"moderately clear\",\n", + " \"very clear\",\n", + " \"completely clear\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Code Efficiency\",\n", + " \"description\": \"Quality of code in terms of efficiency and elegance\",\n", + " \"accepted_values\": [\n", + " \"not at all efficient\",\n", + " \"slightly efficient\",\n", + " \"moderately efficient\",\n", + " \"very efficient\",\n", + " \"extremely efficient\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Code Correctness\",\n", + " \"description\": \"Correctness of the provided code\",\n", + " \"accepted_values\": [\n", + " \"completely incorrect\",\n", + " \"mostly incorrect\",\n", + " \"partly correct\",\n", + " \"mostly correct\",\n", + " \"completely correct\"\n", + " ],\n", + " \"sub_criteria\": []\n", " }\n", - "actual test case to evaluate: {\n", - " \"problem\": \"Each interior angle of a polygon measures 170 degrees. How many sides does the polygon have?\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Prealgebra\",\n", - " \"solution\": \"The sum of the interior angles of a polygon is $180(n-2)$, where $n$ is the number of sides. That means each interior angle has a measure of $\\\\frac{180(n-2)}{n}$ degrees. We set this equal to 170 degrees and solve for $n$. \\\\begin{align*}\\n\\\\frac{180(n-2)}{n}&=170\\\\\\\\\\n\\\\Rightarrow \\\\qquad 180n-360&=170n\\\\\\\\\\\\Rightarrow \\\\qquad 10n&=360\\\\\\\\\\n\\\\Rightarrow\\\\qquad n&=36.\\n\\\\end{align*} The polygon has $\\\\boxed{36}$ sides.\",\n", - " \"problem_id\": \"14\",\n", - " \"response_with_ans\": \"Quick note: A single backtick should be used for inline code, and triple backticks should be used for code blocks. It seems I accidentally used single backticks for the code block in my previous response. Please use the code below instead:\\n\\n```python\\nfrom sympy import symbols, Eq, solve\\n\\nn = symbols('n')\\nequation = Eq(n * 170, (n - 2) * 180)\\nsides = solve(equation, n)[0]\\nprint(sides)\\n```\\n\",\n", - " \"round\": 1,\n", - " \"messages\": [\n", - " {\n", - " \"content\": \"Each interior angle of a polygon measures 170 degrees. How many sides does the polygon have?\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"We can use the formula for the sum of the interior angles of a polygon to solve this problem. The formula is:\\n\\nsum_of_interior_angles = (n - 2) * 180\\n\\nwhere n is the number of sides.\\n\\nAs each interior angle measures 170 degrees, we can write the equation:\\n\\nn * 170 = (n - 2) * 180\\n\\nLet's solve for the number of sides (n) using Python code.\\n\\n```python\\nfrom sympy import symbols, Eq, solve\\n\\nn = symbols('n')\\nequation = Eq(n * 170, (n - 2) * 180)\\nsides = solve(equation, n)[0]\\nsides\\n\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"Quick note: A single backtick should be used for inline code, and triple backticks should be used for code blocks. It seems I accidentally used single backticks for the code block in my previous response. Please use the code below instead:\\n\\n```python\\nfrom sympy import symbols, Eq, solve\\n\\nn = symbols('n')\\nequation = Eq(n * 170, (n - 2) * 180)\\nsides = solve(equation, n)[0]\\nprint(sides)\\n```\\n\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 16.42014193534851,\n", - " \"trial\": -1\n", - "}\n", + "]actual test case to evaluate: {'problem': \"Two numbers are said to be 'relatively prime' if their greatest common factor is 1. How many integers greater than 10 and less than 30 are relatively prime with 28?\", 'level': 'Level 5', 'type': 'Prealgebra', 'solution': 'Since $28=2^2\\\\cdot 7$, a positive integer is relatively prime with $28$ if and only if it contains neither $2$ nor $7$ in its prime factorization. In other words, we want to count the number of integers between $11$ and $29$ inclusive which are divisible by neither $2$ nor $7$.\\n\\nAll of the odd numbers are not divisible by 2; there are 10 such numbers. The only one of these that is divisible by 7 is 21, so there are $10- 1 =\\\\boxed{9}$ numbers between 10 and 30 that are relatively prime with 28.', 'problem_id': '12', 'response_with_ans': 'I will find the relatively prime numbers with 28 in the range greater than 10 and less than 30 using Python code. I will use the math.gcd function and a loop to check each number in the specified range.\\n\\n```python\\nimport math\\n\\ncount = 0\\nrelatively_prime_numbers = []\\nfor number in range(11, 30):\\n if math.gcd(28, number) == 1:\\n relatively_prime_numbers.append(number)\\n count += 1\\n\\nprint(f\"Relatively prime numbers with 28: {relatively_prime_numbers}\")\\nprint(f\"Number of relatively prime numbers with 28: {count}\")\\n```\\nExecute the above code to find the integers relatively prime with 28 and their count.', 'round': 0, 'messages': [{'content': \"Two numbers are said to be 'relatively prime' if their greatest common factor is 1. How many integers greater than 10 and less than 30 are relatively prime with 28?\", 'role': 'user'}, {'content': 'I will find the relatively prime numbers with 28 in the range greater than 10 and less than 30 using Python code. I will use the math.gcd function and a loop to check each number in the specified range.\\n\\n```python\\nimport math\\n\\ncount = 0\\nrelatively_prime_numbers = []\\nfor number in range(11, 30):\\n if math.gcd(28, number) == 1:\\n relatively_prime_numbers.append(number)\\n count += 1\\n\\nprint(f\"Relatively prime numbers with 28: {relatively_prime_numbers}\")\\nprint(f\"Number of relatively prime numbers with 28: {count}\")\\n```\\nExecute the above code to find the integers relatively prime with 28 and their count.', 'role': 'assistant'}], 'time': 6.9820802211761475, 'trial': -1}\n", "\n", "--------------------------------------------------------------------------------\n", + "\u001b[31m\n", + ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", "\u001b[33mquantifier\u001b[0m (to quantifier_user):\n", "\n", "{\n", " \"Problem Interpretation\": \"completely accurate\",\n", " \"Mathematical Methodology\": \"completely effective\",\n", " \"Calculation Correctness\": \"completely correct\",\n", - " \"Explanation Clarity\": \"completely clear\",\n", - " \"Code Efficiency\": \"very efficient\",\n", + " \"Explanation Clarity\": \"very clear\",\n", + " \"Code Efficiency\": \"moderately efficient\",\n", " \"Code Correctness\": \"completely correct\"\n", "}\n", "\n", "--------------------------------------------------------------------------------\n", - "actual label for this case: false\n", "\u001b[33mquantifier_user\u001b[0m (to quantifier):\n", "\n", "Task: Math problem solving.\n", - "Task description: Given any question, the system needs to solve the problem as consisely and accurately as possible\n", - "Task successful example: {\n", - " \"problem\": \"What is the sum of all the distinct positive two-digit factors of 144?\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Number Theory\",\n", - " \"solution\": \"Prime factorize $144=2^4\\\\cdot3^2$. The sum of the positive two-digit factors of 144 is $2^4+2\\\\cdot3^2+2^2\\\\cdot3+2^2\\\\cdot3^2+2^3\\\\cdot3+2^3\\\\cdot3^2+2^4\\\\cdot3=\\\\boxed{226}.$\",\n", - " \"problem_id\": \"0\",\n", - " \"response_with_ans\": \"To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere's a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\\\"The sum of all the distinct positive two-digit factors of 144 is:\\\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.\",\n", - " \"round\": 0,\n", - " \"messages\": [\n", - " {\n", - " \"content\": \"What is the sum of all the distinct positive two-digit factors of 144?\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere's a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\\\"The sum of all the distinct positive two-digit factors of 144 is:\\\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 11.140539407730103,\n", - " \"trial\": -1\n", - "}\n", - "\n", - "Task failed example: {\n", - " \"problem\": \"Find all $x$ that satisfy the inequality $(2x+10)(x+3)<(3x+9)(x+8)$. Express your answer in interval notation.\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Algebra\",\n", - " \"solution\": \"We have \\\\begin{align*} (2x+10)(x+3)&<(3x+9)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)&<3(x+3)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)-3(x+3)(x+8)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (2x+10-(3x+24))(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (-x-14)(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (x+14)(x+3)&>0.\\n\\\\end{align*} This inequality is satisfied if and only if $(x+14)$ and $(x+3)$ are either both positive or both negative. Both factors are positive for $x>-3$ and both factors are negative for $x<-14$. When $-14 0$ and then find intervals where the inequality holds true.\\n\\nLet's first find the factorized form of the quadratic:\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"I apologize for the confusion in my previous message. Let's find the factorized form of the quadratic using Python code.\\n\\n```python\\nfrom sympy import symbols, factor\\r\\nx = symbols('x')\\r\\nfactorized = factor(x**2 + 17*x + 42)\\r\\nfactorized\\r\\n```\\n\\nPlease run the Python code above to find the factorized form of the quadratic, and we can proceed to determine the intervals where the inequality holds true.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 24.91333508491516,\n", - " \"trial\": -1\n", - "}\n", - "\n", - "Evaluation dictionary: {\n", - " \"Problem Interpretation\": {\n", - " \"description\": \"Ability to correctly interpret the problem.\",\n", - " \"accepted_values\": [\"completely off\", \"slightly relevant\", \"relevant\", \"mostly accurate\", \"completely accurate\"]\n", - " },\n", - " \"Mathematical Methodology\": {\n", - " \"description\": \"Adequacy of the chosen mathematical or algorithmic methodology for the question\",\n", - " \"accepted_values\": [\"inappropriate\", \"barely adequate\", \"adequate\", \"mostly effective\", \"completely effective\"]\n", - " },\n", - " \"Calculation Correctness\": {\n", - " \"description\": \"Accuracy of calculations made and solutions given\",\n", - " \"accepted_values\": [\"completely incorrect\", \"mostly incorrect\", \"neither\", \"mostly correct\", \"completely correct\"]\n", - " },\n", - " \"Explanation Clarity\": {\n", - " \"description\": \"Clarity and comprehensibility of explanations, including language use and structure\",\n", - " \"accepted_values\": [\"not at all clear\", \"slightly clear\", \"moderately clear\", \"very clear\", \"completely clear\"]\n", - " },\n", - " \"Code Efficiency\": {\n", - " \"description\": \"Quality of code in terms of efficiency and elegance\",\n", - " \"accepted_values\": [\"not at all efficient\", \"slightly efficient\", \"moderately efficient\", \"very efficient\", \"extremely efficient\"]\n", - " },\n", - " \"Code Correctness\": {\n", - " \"description\": \"Correctness of the provided code\",\n", - " \"accepted_values\": [\"completely incorrect\", \"mostly incorrect\", \"partly correct\", \"mostly correct\", \"completely correct\"]\n", - " }\n", + " Task description: Given any question, the system needs to solve the problem as consisely and accurately as possible\n", + " Task successful example: {'problem': 'What is the sum of all the distinct positive two-digit factors of 144?', 'level': 'Level 5', 'type': 'Number Theory', 'solution': 'Prime factorize $144=2^4\\\\cdot3^2$. The sum of the positive two-digit factors of 144 is $2^4+2\\\\cdot3^2+2^2\\\\cdot3+2^2\\\\cdot3^2+2^3\\\\cdot3+2^3\\\\cdot3^2+2^4\\\\cdot3=\\\\boxed{226}.$', 'problem_id': '0', 'response_with_ans': 'To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere\\'s a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\"The sum of all the distinct positive two-digit factors of 144 is:\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.', 'round': 0, 'messages': [{'content': 'What is the sum of all the distinct positive two-digit factors of 144?', 'role': 'user'}, {'content': 'To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere\\'s a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\"The sum of all the distinct positive two-digit factors of 144 is:\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.', 'role': 'assistant'}], 'time': 11.140539407730103, 'trial': -1}\n", + " Task failed example: {'problem': 'Find all $x$ that satisfy the inequality $(2x+10)(x+3)<(3x+9)(x+8)$. Express your answer in interval notation.', 'level': 'Level 5', 'type': 'Algebra', 'solution': 'We have \\\\begin{align*} (2x+10)(x+3)&<(3x+9)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)&<3(x+3)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)-3(x+3)(x+8)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (2x+10-(3x+24))(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (-x-14)(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (x+14)(x+3)&>0.\\n\\\\end{align*} This inequality is satisfied if and only if $(x+14)$ and $(x+3)$ are either both positive or both negative. Both factors are positive for $x>-3$ and both factors are negative for $x<-14$. When $-14 0$ and then find intervals where the inequality holds true.\\n\\nLet's first find the factorized form of the quadratic:\", 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': \"I apologize for the confusion in my previous message. Let's find the factorized form of the quadratic using Python code.\\n\\n```python\\nfrom sympy import symbols, factor\\r\\nx = symbols('x')\\r\\nfactorized = factor(x**2 + 17*x + 42)\\r\\nfactorized\\r\\n```\\n\\nPlease run the Python code above to find the factorized form of the quadratic, and we can proceed to determine the intervals where the inequality holds true.\", 'role': 'assistant'}], 'time': 24.91333508491516, 'trial': -1}\n", + " Evaluation dictionary: [\n", + " {\n", + " \"name\": \"Problem Interpretation\",\n", + " \"description\": \"Ability to correctly interpret the problem.\",\n", + " \"accepted_values\": [\n", + " \"completely off\",\n", + " \"slightly relevant\",\n", + " \"relevant\",\n", + " \"mostly accurate\",\n", + " \"completely accurate\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Mathematical Methodology\",\n", + " \"description\": \"Adequacy of the chosen mathematical or algorithmic methodology for the question\",\n", + " \"accepted_values\": [\n", + " \"inappropriate\",\n", + " \"barely adequate\",\n", + " \"adequate\",\n", + " \"mostly effective\",\n", + " \"completely effective\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Calculation Correctness\",\n", + " \"description\": \"Accuracy of calculations made and solutions given\",\n", + " \"accepted_values\": [\n", + " \"completely incorrect\",\n", + " \"mostly incorrect\",\n", + " \"neither\",\n", + " \"mostly correct\",\n", + " \"completely correct\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Explanation Clarity\",\n", + " \"description\": \"Clarity and comprehensibility of explanations, including language use and structure\",\n", + " \"accepted_values\": [\n", + " \"not at all clear\",\n", + " \"slightly clear\",\n", + " \"moderately clear\",\n", + " \"very clear\",\n", + " \"completely clear\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Code Efficiency\",\n", + " \"description\": \"Quality of code in terms of efficiency and elegance\",\n", + " \"accepted_values\": [\n", + " \"not at all efficient\",\n", + " \"slightly efficient\",\n", + " \"moderately efficient\",\n", + " \"very efficient\",\n", + " \"extremely efficient\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Code Correctness\",\n", + " \"description\": \"Correctness of the provided code\",\n", + " \"accepted_values\": [\n", + " \"completely incorrect\",\n", + " \"mostly incorrect\",\n", + " \"partly correct\",\n", + " \"mostly correct\",\n", + " \"completely correct\"\n", + " ],\n", + " \"sub_criteria\": []\n", " }\n", - "actual test case to evaluate: {\n", - " \"problem\": \"Simplify $\\\\frac{2m+8}{3}-\\\\frac{2-m}{3}$.\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Prealgebra\",\n", - " \"solution\": \"Both fractions have the same denominator, so we can subtract them: \\\\[\\\\frac{2m+8}{3}-\\\\frac{2-m}{3}=\\\\frac{(2m+8)-(2-m)}{3}\\\\] Distributing the negative sign across the parentheses, we get \\\\[\\\\frac{2m+8-2-(-m)}{3}=\\\\frac{2m+8-2+m}{3}=\\\\frac{3m+6}{3}\\\\] Notice that every number in the numerator has a common factor of 3. We can use the distributive law in reverse to get \\\\[\\\\frac{3m+6}{3}=\\\\frac{3(m+2)}{3}=\\\\frac{\\\\cancel{3}(m+2)}{\\\\cancel{3}}=\\\\boxed{m+2}.\\\\]\",\n", - " \"problem_id\": \"19\",\n", - " \"response_with_ans\": \"\",\n", - " \"round\": 15,\n", - " \"messages\": [\n", - " {\n", - " \"content\": \"Simplify $\\\\frac{2m+8}{3}-\\\\frac{2-m}{3}$.\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"To simplify the expression, we can combine the fractions as they have the same denominator:\\n\\n$\\\\frac{2m + 8}{3} - \\\\frac{2 - m}{3} = \\\\frac{(2m + 8) - (2 - m)}{3}$\\n\\nNow, I will calculate the numerator and finish simplifying the expression.\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"\\n\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"My apologies for the confusion in the previous responses. Let me simplify the expression step by step now. \\n\\nWe have the expression:\\n\\n$\\\\frac{2m + 8}{3} - \\\\frac{2 - m}{3}$\\n\\nWe can combine the fractions as they have the same denominator:\\n\\n$\\\\frac{(2m + 8) - (2 - m)}{3}$\\n\\nLet's further simplify the expression using Python code.\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 37.317880630493164,\n", - " \"trial\": -1\n", - "}\n", + "]actual test case to evaluate: {'problem': 'What is the product of $0.\\\\overline{05}$ and $1.8$ expressed as a fraction in simplest form?', 'level': 'Level 5', 'type': 'Prealgebra', 'solution': \"First let's convert $0.\\\\overline{05}$ to a fraction. Let's define $x=0.\\\\overline{05}$. If we multiply both sides by $100$ we get $100x=5.\\\\overline{05}$ so $99x=5$ and $x=0.\\\\overline{05}=\\\\frac{5}{99}$. Since $1.8=\\\\frac{9}{5}$ we get $$\\\\frac{\\\\cancel{5}}{99}\\\\cdot\\\\frac{9}{\\\\cancel{5}}=\\\\frac{9}{99}=\\\\boxed{\\\\frac{1}{11}}.$$\", 'problem_id': '9', 'response_with_ans': '```python\\nfrom fractions import Fraction\\n\\nfraction_1 = Fraction(5, 99)\\ndecimal_2 = 1.8\\n\\nproduct = fraction_1 * decimal_2\\n\\nsimplified_fraction = Fraction(product).limit_denominator()\\n\\nprint(simplified_fraction)\\n```\\n\\nPlease run the above Python code to find the product of $\\\\frac{5}{99}$ and $1.8$ expressed as a fraction in simplest form.', 'round': 1, 'messages': [{'content': 'What is the product of $0.\\\\overline{05}$ and $1.8$ expressed as a fraction in simplest form?', 'role': 'user'}, {'content': \"To find the product of $0.\\\\overline{05}$ and $1.8$, first we need to convert the repeating decimal $0.\\\\overline{05}$ into a fraction.\\n\\nLet's call the repeating decimal $x$:\\n\\n$$x = 0.\\\\overline{05}$$\\n\\nTo convert the repeating decimal into a fraction, we can multiply both sides by $100$ to shift the decimal point two places to the right:\\n\\n$$100x = 5.\\\\overline{05}$$\\n\\nNow, subtract the first equation from the second equation:\\n\\n$$100x - x = 5.\\\\overline{05} - 0.\\\\overline{05}$$\\n$$99x = 5$$\\n\\nNow, we can solve for $x$:\\n\\n$$x = \\\\frac{5}{99}$$\\n\\nSo the repeating decimal $0.\\\\overline{05}$ is equivalent to the fraction $\\\\frac{5}{99}$.\\n\\nNow, we can find the product of $\\\\frac{5}{99}$ and $1.8$:\\n\\n$$\\\\frac{5}{99} \\\\times 1.8$$\\n\\nFinally, we will simplify the fraction. Let's write the code to compute the product and simplify the result as a fraction.\", 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': '```python\\nfrom fractions import Fraction\\n\\nfraction_1 = Fraction(5, 99)\\ndecimal_2 = 1.8\\n\\nproduct = fraction_1 * decimal_2\\n\\nsimplified_fraction = Fraction(product).limit_denominator()\\n\\nprint(simplified_fraction)\\n```\\n\\nPlease run the above Python code to find the product of $\\\\frac{5}{99}$ and $1.8$ expressed as a fraction in simplest form.', 'role': 'assistant'}], 'time': 18.04914665222168, 'trial': -1}\n", "\n", "--------------------------------------------------------------------------------\n", + "\u001b[31m\n", + ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", "\u001b[33mquantifier\u001b[0m (to quantifier_user):\n", "\n", "{\n", " \"Problem Interpretation\": \"completely accurate\",\n", " \"Mathematical Methodology\": \"completely effective\",\n", " \"Calculation Correctness\": \"completely correct\",\n", - " \"Explanation Clarity\": \"not at all clear\",\n", - " \"Code Efficiency\": \"not applicable\",\n", - " \"Code Correctness\": \"not applicable\"\n", + " \"Explanation Clarity\": \"very clear\",\n", + " \"Code Efficiency\": \"moderately efficient\",\n", + " \"Code Correctness\": \"completely correct\"\n", "}\n", "\n", "--------------------------------------------------------------------------------\n", - "actual label for this case: true\n", - "\u001b[33mquantifier_user\u001b[0m (to quantifier):\n", - "\n", - "Task: Math problem solving.\n", - "Task description: Given any question, the system needs to solve the problem as consisely and accurately as possible\n", - "Task successful example: {\n", - " \"problem\": \"What is the sum of all the distinct positive two-digit factors of 144?\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Number Theory\",\n", - " \"solution\": \"Prime factorize $144=2^4\\\\cdot3^2$. The sum of the positive two-digit factors of 144 is $2^4+2\\\\cdot3^2+2^2\\\\cdot3+2^2\\\\cdot3^2+2^3\\\\cdot3+2^3\\\\cdot3^2+2^4\\\\cdot3=\\\\boxed{226}.$\",\n", - " \"problem_id\": \"0\",\n", - " \"response_with_ans\": \"To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere's a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\\\"The sum of all the distinct positive two-digit factors of 144 is:\\\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.\",\n", - " \"round\": 0,\n", - " \"messages\": [\n", - " {\n", - " \"content\": \"What is the sum of all the distinct positive two-digit factors of 144?\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere's a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\\\"The sum of all the distinct positive two-digit factors of 144 is:\\\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 11.140539407730103,\n", - " \"trial\": -1\n", - "}\n", - "\n", - "Task failed example: {\n", - " \"problem\": \"Find all $x$ that satisfy the inequality $(2x+10)(x+3)<(3x+9)(x+8)$. Express your answer in interval notation.\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Algebra\",\n", - " \"solution\": \"We have \\\\begin{align*} (2x+10)(x+3)&<(3x+9)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)&<3(x+3)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)-3(x+3)(x+8)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (2x+10-(3x+24))(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (-x-14)(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (x+14)(x+3)&>0.\\n\\\\end{align*} This inequality is satisfied if and only if $(x+14)$ and $(x+3)$ are either both positive or both negative. Both factors are positive for $x>-3$ and both factors are negative for $x<-14$. When $-14 0$ and then find intervals where the inequality holds true.\\n\\nLet's first find the factorized form of the quadratic:\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"I apologize for the confusion in my previous message. Let's find the factorized form of the quadratic using Python code.\\n\\n```python\\nfrom sympy import symbols, factor\\r\\nx = symbols('x')\\r\\nfactorized = factor(x**2 + 17*x + 42)\\r\\nfactorized\\r\\n```\\n\\nPlease run the Python code above to find the factorized form of the quadratic, and we can proceed to determine the intervals where the inequality holds true.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 24.91333508491516,\n", - " \"trial\": -1\n", - "}\n", - "\n", - "Evaluation dictionary: {\n", - " \"Problem Interpretation\": {\n", - " \"description\": \"Ability to correctly interpret the problem.\",\n", - " \"accepted_values\": [\"completely off\", \"slightly relevant\", \"relevant\", \"mostly accurate\", \"completely accurate\"]\n", - " },\n", - " \"Mathematical Methodology\": {\n", - " \"description\": \"Adequacy of the chosen mathematical or algorithmic methodology for the question\",\n", - " \"accepted_values\": [\"inappropriate\", \"barely adequate\", \"adequate\", \"mostly effective\", \"completely effective\"]\n", - " },\n", - " \"Calculation Correctness\": {\n", - " \"description\": \"Accuracy of calculations made and solutions given\",\n", - " \"accepted_values\": [\"completely incorrect\", \"mostly incorrect\", \"neither\", \"mostly correct\", \"completely correct\"]\n", - " },\n", - " \"Explanation Clarity\": {\n", - " \"description\": \"Clarity and comprehensibility of explanations, including language use and structure\",\n", - " \"accepted_values\": [\"not at all clear\", \"slightly clear\", \"moderately clear\", \"very clear\", \"completely clear\"]\n", - " },\n", - " \"Code Efficiency\": {\n", - " \"description\": \"Quality of code in terms of efficiency and elegance\",\n", - " \"accepted_values\": [\"not at all efficient\", \"slightly efficient\", \"moderately efficient\", \"very efficient\", \"extremely efficient\"]\n", - " },\n", - " \"Code Correctness\": {\n", - " \"description\": \"Correctness of the provided code\",\n", - " \"accepted_values\": [\"completely incorrect\", \"mostly incorrect\", \"partly correct\", \"mostly correct\", \"completely correct\"]\n", - " }\n", - " }\n", - "actual test case to evaluate: {\n", - " \"problem\": \"A $30^\\\\circ$-$60^\\\\circ$-$90^\\\\circ$ triangle is drawn on the exterior of an equilateral triangle so the hypotenuse of the right triangle is one side of the equilateral triangle. If the shorter leg of the right triangle is 6 units, what is the distance between the two vertices that the triangles do not have in common? Express your answer in simplest radical form. [asy]\\ndraw((2,0)--(0,0)--(1,1.732)--(2,1.732)--(2,0)--(1,1.732));\\ndraw((2,1.632)--(1.9,1.632)--(1.9,1.732));\\nlabel(\\\"$60^\\\\circ$\\\",(1,1.732),2SE+E);\\nlabel(\\\"$30^\\\\circ$\\\",(2,0),5NNW+4N);\\nlabel(\\\"6\\\",(1.5,1.732),N);\\n[/asy]\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Prealgebra\",\n", - " \"solution\": \"Multiply the short leg of the right triangle by $\\\\sqrt{3}$ to find that the length of the longer leg is $6\\\\sqrt{3}$ units. Double the short leg of the right triangle to find that the length of the hypotenuse of the right triangle is 12 units. Since the hypotenuse of the right triangle is a side of the equilateral triangle, the side length of the equilateral triangle is also 12 units. By the Pythagorean theorem, the distance between the two vertices that the two triangles do not have in common is $\\\\sqrt{(6\\\\sqrt{3})^2+12^2}=\\\\sqrt{252}=\\\\boxed{6\\\\sqrt{7}}$ units. [asy]\\ndraw((2,0)--(0,0)--(1,sqrt(3))--(2,sqrt(3))--(2,0)--(1,sqrt(3)));\\ndraw((2,sqrt(3)-0.1)--(1.9,sqrt(3)-0.1)--(1.9,sqrt(3)));\\ndraw((0,0)--(2,sqrt(3)));\\nlabel(\\\"$60^\\\\circ$\\\",(1,sqrt(3)),2SE+E);\\nlabel(\\\"$30^\\\\circ$\\\",(2,0),5NNW+4N);\\nlabel(\\\"6\\\",(1.5,sqrt(3)),N);\\nlabel(\\\"$6\\\\sqrt{3}$\\\",(2,sqrt(3)/2),E);\\nlabel(\\\"12\\\",(1.5,sqrt(3)/2),SW);\\nlabel(\\\"12\\\",(1,0),S);\\n[/asy]\",\n", - " \"problem_id\": \"7\",\n", - " \"response_with_ans\": \"We have found the distance between the two vertices that the triangles do not have in common (C and D):\\n\\nx = \\u221a(252 + 72\\u221a3)\\n\\nThis is the simplest radical form for the required distance. \\n\\nTERMINATE\",\n", - " \"round\": 3,\n", - " \"messages\": [\n", - " {\n", - " \"content\": \"A $30^\\\\circ$-$60^\\\\circ$-$90^\\\\circ$ triangle is drawn on the exterior of an equilateral triangle so the hypotenuse of the right triangle is one side of the equilateral triangle. If the shorter leg of the right triangle is 6 units, what is the distance between the two vertices that the triangles do not have in common? Express your answer in simplest radical form. [asy]\\ndraw((2,0)--(0,0)--(1,1.732)--(2,1.732)--(2,0)--(1,1.732));\\ndraw((2,1.632)--(1.9,1.632)--(1.9,1.732));\\nlabel(\\\"$60^\\\\circ$\\\",(1,1.732),2SE+E);\\nlabel(\\\"$30^\\\\circ$\\\",(2,0),5NNW+4N);\\nlabel(\\\"6\\\",(1.5,1.732),N);\\n[/asy]\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"Let's denote the vertices of the equilateral triangle as A, B, and C, where A is the right angle of the $30^\\\\circ$-$60^\\\\circ$-$90^\\\\circ$ triangle, B is adjacent to the $30^\\\\circ$ angle, and C is adjacent to the $60^\\\\circ$ angle. Let D be the vertex of the right triangle that is not a part of the equilateral triangle (the $60^\\\\circ$ angle).\\n\\nSince the shorter leg of the right triangle is 6 units (AD) and it is a $30^\\\\circ$-$60^\\\\circ$-$90^\\\\circ$ triangle, we know that:\\n1. The longer leg (BD) has a length equal to the shorter leg times $\\\\sqrt{3}$ ($6\\\\sqrt{3}$ units).\\n2. The hypotenuse (AB) has a length twice the size of the shorter leg (12 units).\\n\\nNow, we are trying to find the distance between vertices C and D. Let's call this distance x.\\n\\nTo calculate x, we can use the law of cosines on triangle $\\\\bigtriangleup BCD$. Here, the angle at vertex B is $180^\\\\circ - 60^\\\\circ = 120^\\\\circ$. We have the lengths of sides BC and BD, so we can calculate x using the law of cosines and then simplify the result.\\n\\nThe law of cosines formula is:\\nx^2 = (BC^2) + (BD^2) - 2(BC)(BD)cos(120^\\\\circ)\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"First, we need the length of side BC, which is equal to the length of side AB (since it is an equilateral triangle). Thus, BC = 12 units. Now we can use the law of cosines formula to find the value of x^2:\\n\\nx^2 = (12^2) + (6\\u221a3)^2 - 2(12)(6\\u221a3)cos(120\\u00ba)\\n\\nThe cosine of 120\\u00ba is -1/2, so:\\n\\nx^2 = 144 + 108 - 2(12)(6\\u221a3)(-1/2)\\n\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"Let's simplify the expression for x^2:\\n\\nx^2 = 144 + 108 + (12)(6\\u221a3)\\n\\nNow we will calculate the result:\\n\\nx^2 = 144 + 108 + 72\\u221a3\\n\\nx^2 = 252 + 72\\u221a3\\n\\nNow, to find the value of x, we'll take the square root of both sides:\\n\\nx = \\u221a(252 + 72\\u221a3)\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"We have found the distance between the two vertices that the triangles do not have in common (C and D):\\n\\nx = \\u221a(252 + 72\\u221a3)\\n\\nThis is the simplest radical form for the required distance. \\n\\nTERMINATE\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 35.88237428665161,\n", - " \"trial\": -1\n", - "}\n", + "\u001b[33mquantifier_user\u001b[0m (to quantifier):\n", + "\n", + "Task: Math problem solving.\n", + " Task description: Given any question, the system needs to solve the problem as consisely and accurately as possible\n", + " Task successful example: {'problem': 'What is the sum of all the distinct positive two-digit factors of 144?', 'level': 'Level 5', 'type': 'Number Theory', 'solution': 'Prime factorize $144=2^4\\\\cdot3^2$. The sum of the positive two-digit factors of 144 is $2^4+2\\\\cdot3^2+2^2\\\\cdot3+2^2\\\\cdot3^2+2^3\\\\cdot3+2^3\\\\cdot3^2+2^4\\\\cdot3=\\\\boxed{226}.$', 'problem_id': '0', 'response_with_ans': 'To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere\\'s a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\"The sum of all the distinct positive two-digit factors of 144 is:\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.', 'round': 0, 'messages': [{'content': 'What is the sum of all the distinct positive two-digit factors of 144?', 'role': 'user'}, {'content': 'To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere\\'s a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\"The sum of all the distinct positive two-digit factors of 144 is:\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.', 'role': 'assistant'}], 'time': 11.140539407730103, 'trial': -1}\n", + " Task failed example: {'problem': 'Find all $x$ that satisfy the inequality $(2x+10)(x+3)<(3x+9)(x+8)$. Express your answer in interval notation.', 'level': 'Level 5', 'type': 'Algebra', 'solution': 'We have \\\\begin{align*} (2x+10)(x+3)&<(3x+9)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)&<3(x+3)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)-3(x+3)(x+8)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (2x+10-(3x+24))(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (-x-14)(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (x+14)(x+3)&>0.\\n\\\\end{align*} This inequality is satisfied if and only if $(x+14)$ and $(x+3)$ are either both positive or both negative. Both factors are positive for $x>-3$ and both factors are negative for $x<-14$. When $-14 0$ and then find intervals where the inequality holds true.\\n\\nLet's first find the factorized form of the quadratic:\", 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': \"I apologize for the confusion in my previous message. Let's find the factorized form of the quadratic using Python code.\\n\\n```python\\nfrom sympy import symbols, factor\\r\\nx = symbols('x')\\r\\nfactorized = factor(x**2 + 17*x + 42)\\r\\nfactorized\\r\\n```\\n\\nPlease run the Python code above to find the factorized form of the quadratic, and we can proceed to determine the intervals where the inequality holds true.\", 'role': 'assistant'}], 'time': 24.91333508491516, 'trial': -1}\n", + " Evaluation dictionary: [\n", + " {\n", + " \"name\": \"Problem Interpretation\",\n", + " \"description\": \"Ability to correctly interpret the problem.\",\n", + " \"accepted_values\": [\n", + " \"completely off\",\n", + " \"slightly relevant\",\n", + " \"relevant\",\n", + " \"mostly accurate\",\n", + " \"completely accurate\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Mathematical Methodology\",\n", + " \"description\": \"Adequacy of the chosen mathematical or algorithmic methodology for the question\",\n", + " \"accepted_values\": [\n", + " \"inappropriate\",\n", + " \"barely adequate\",\n", + " \"adequate\",\n", + " \"mostly effective\",\n", + " \"completely effective\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Calculation Correctness\",\n", + " \"description\": \"Accuracy of calculations made and solutions given\",\n", + " \"accepted_values\": [\n", + " \"completely incorrect\",\n", + " \"mostly incorrect\",\n", + " \"neither\",\n", + " \"mostly correct\",\n", + " \"completely correct\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Explanation Clarity\",\n", + " \"description\": \"Clarity and comprehensibility of explanations, including language use and structure\",\n", + " \"accepted_values\": [\n", + " \"not at all clear\",\n", + " \"slightly clear\",\n", + " \"moderately clear\",\n", + " \"very clear\",\n", + " \"completely clear\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Code Efficiency\",\n", + " \"description\": \"Quality of code in terms of efficiency and elegance\",\n", + " \"accepted_values\": [\n", + " \"not at all efficient\",\n", + " \"slightly efficient\",\n", + " \"moderately efficient\",\n", + " \"very efficient\",\n", + " \"extremely efficient\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Code Correctness\",\n", + " \"description\": \"Correctness of the provided code\",\n", + " \"accepted_values\": [\n", + " \"completely incorrect\",\n", + " \"mostly incorrect\",\n", + " \"partly correct\",\n", + " \"mostly correct\",\n", + " \"completely correct\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " }\n", + "]actual test case to evaluate: {'problem': 'All 50 states as well as the District of Columbia and Puerto Rico, have distinct two-letter postal abbreviations. If a two-letter sequence of letters (such as CO or EE) is chosen at random, what is the probability that it is a postal abbreviation for one of the 50 states, the District of Columbia, or Puerto Rico? Express your answer as a common fraction.', 'level': 'Level 5', 'type': 'Prealgebra', 'solution': 'There are $26\\\\cdot 26$ possible two-letter sequences of letters, since we have 26 choices for the first and then 26 choices for the second. But only 52 of these possibilities are valid, so our answer is $\\\\frac{52}{26\\\\cdot 26} =\\\\boxed{ \\\\frac{1}{13}}$.', 'problem_id': '8', 'response_with_ans': 'We\\'ll use a reliable source like USPS to fetch the postal abbreviations, and then calculate the probability.\\n\\n```python\\nimport requests\\nfrom bs4 import BeautifulSoup\\nimport fractions\\n\\nurl = \\'https://pe.usps.com/text/pub28/28apb.htm\\'\\nresponse = requests.get(url)\\nsoup = BeautifulSoup(response.text, \\'html.parser\\')\\n\\n# Find the table containing the state info\\nstate_table = soup.find_all(\\'table\\')[0]\\n\\n# Extract state abbreviations from the table\\nstate_abbreviations = [row.find_all(\\'td\\')[-1].text.strip() for row in state_table.find_all(\\'tr\\')[3:-2]]\\n\\n# Add District of Columbia and Puerto Rico.\\nstate_abbreviations.extend([\"DC\", \"PR\"])\\n\\nnum_valid_abbreviations = len(state_abbreviations)\\ntotal_possible_combinations = 26 * 26\\nprobability = fractions.Fraction(num_valid_abbreviations, total_possible_combinations)\\n\\nprint(f\"Valid postal abbreviations: {state_abbreviations}\")\\nprint(f\"Total valid postal abbreviations: {num_valid_abbreviations}\")\\nprint(f\"Total possible two-letter sequences: {total_possible_combinations}\")\\nprint(f\"Probability: {probability}\")\\n```\\n\\nPlease run the above code, and the output will show you the list of valid postal abbreviations, the total number of valid abbreviations, the total possible two-letter sequences, and the probability.', 'round': 1, 'messages': [{'content': 'All 50 states as well as the District of Columbia and Puerto Rico, have distinct two-letter postal abbreviations. If a two-letter sequence of letters (such as CO or EE) is chosen at random, what is the probability that it is a postal abbreviation for one of the 50 states, the District of Columbia, or Puerto Rico? Express your answer as a common fraction.', 'role': 'user'}, {'content': \"To calculate the probability, we first need to know the total number of possible two-letter sequences and the number of valid postal abbreviations.\\n\\nTo get the total number of possible two-letter sequences, we'll calculate all possible combinations using 26 letters of the alphabet. There are 26 options for choosing the first letter and 26 options for choosing the second letter, so the possible sequences are 26 * 26.\\n\\nNext, let's find the valid postal abbreviations for all 50 states, District of Columbia, and Puerto Rico. To do this, we can create code to download the postal abbreviations.\", 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': 'We\\'ll use a reliable source like USPS to fetch the postal abbreviations, and then calculate the probability.\\n\\n```python\\nimport requests\\nfrom bs4 import BeautifulSoup\\nimport fractions\\n\\nurl = \\'https://pe.usps.com/text/pub28/28apb.htm\\'\\nresponse = requests.get(url)\\nsoup = BeautifulSoup(response.text, \\'html.parser\\')\\n\\n# Find the table containing the state info\\nstate_table = soup.find_all(\\'table\\')[0]\\n\\n# Extract state abbreviations from the table\\nstate_abbreviations = [row.find_all(\\'td\\')[-1].text.strip() for row in state_table.find_all(\\'tr\\')[3:-2]]\\n\\n# Add District of Columbia and Puerto Rico.\\nstate_abbreviations.extend([\"DC\", \"PR\"])\\n\\nnum_valid_abbreviations = len(state_abbreviations)\\ntotal_possible_combinations = 26 * 26\\nprobability = fractions.Fraction(num_valid_abbreviations, total_possible_combinations)\\n\\nprint(f\"Valid postal abbreviations: {state_abbreviations}\")\\nprint(f\"Total valid postal abbreviations: {num_valid_abbreviations}\")\\nprint(f\"Total possible two-letter sequences: {total_possible_combinations}\")\\nprint(f\"Probability: {probability}\")\\n```\\n\\nPlease run the above code, and the output will show you the list of valid postal abbreviations, the total number of valid abbreviations, the total possible two-letter sequences, and the probability.', 'role': 'assistant'}], 'time': 27.01692771911621, 'trial': -1}\n", "\n", "--------------------------------------------------------------------------------\n", + "\u001b[31m\n", + ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", "\u001b[33mquantifier\u001b[0m (to quantifier_user):\n", "\n", + "```json\n", "{\n", - " \"Problem Interpretation\": \"mostly accurate\",\n", + " \"Problem Interpretation\": \"completely accurate\",\n", " \"Mathematical Methodology\": \"completely effective\",\n", - " \"Calculation Correctness\": \"mostly correct\",\n", - " \"Explanation Clarity\": \"moderately clear\",\n", - " \"Code Efficiency\": \"not applicable\",\n", - " \"Code Correctness\": \"not applicable\"\n", + " \"Calculation Correctness\": \"completely correct\",\n", + " \"Explanation Clarity\": \"very clear\",\n", + " \"Code Efficiency\": \"moderately efficient\",\n", + " \"Code Correctness\": \"completely correct\"\n", "}\n", + "```\n", "\n", "--------------------------------------------------------------------------------\n", - "actual label for this case: true\n", "\u001b[33mquantifier_user\u001b[0m (to quantifier):\n", "\n", "Task: Math problem solving.\n", - "Task description: Given any question, the system needs to solve the problem as consisely and accurately as possible\n", - "Task successful example: {\n", - " \"problem\": \"What is the sum of all the distinct positive two-digit factors of 144?\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Number Theory\",\n", - " \"solution\": \"Prime factorize $144=2^4\\\\cdot3^2$. The sum of the positive two-digit factors of 144 is $2^4+2\\\\cdot3^2+2^2\\\\cdot3+2^2\\\\cdot3^2+2^3\\\\cdot3+2^3\\\\cdot3^2+2^4\\\\cdot3=\\\\boxed{226}.$\",\n", - " \"problem_id\": \"0\",\n", - " \"response_with_ans\": \"To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere's a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\\\"The sum of all the distinct positive two-digit factors of 144 is:\\\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.\",\n", - " \"round\": 0,\n", - " \"messages\": [\n", - " {\n", - " \"content\": \"What is the sum of all the distinct positive two-digit factors of 144?\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere's a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\\\"The sum of all the distinct positive two-digit factors of 144 is:\\\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 11.140539407730103,\n", - " \"trial\": -1\n", - "}\n", - "\n", - "Task failed example: {\n", - " \"problem\": \"Find all $x$ that satisfy the inequality $(2x+10)(x+3)<(3x+9)(x+8)$. Express your answer in interval notation.\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Algebra\",\n", - " \"solution\": \"We have \\\\begin{align*} (2x+10)(x+3)&<(3x+9)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)&<3(x+3)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)-3(x+3)(x+8)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (2x+10-(3x+24))(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (-x-14)(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (x+14)(x+3)&>0.\\n\\\\end{align*} This inequality is satisfied if and only if $(x+14)$ and $(x+3)$ are either both positive or both negative. Both factors are positive for $x>-3$ and both factors are negative for $x<-14$. When $-14 0$ and then find intervals where the inequality holds true.\\n\\nLet's first find the factorized form of the quadratic:\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"I apologize for the confusion in my previous message. Let's find the factorized form of the quadratic using Python code.\\n\\n```python\\nfrom sympy import symbols, factor\\r\\nx = symbols('x')\\r\\nfactorized = factor(x**2 + 17*x + 42)\\r\\nfactorized\\r\\n```\\n\\nPlease run the Python code above to find the factorized form of the quadratic, and we can proceed to determine the intervals where the inequality holds true.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 24.91333508491516,\n", - " \"trial\": -1\n", - "}\n", - "\n", - "Evaluation dictionary: {\n", - " \"Problem Interpretation\": {\n", - " \"description\": \"Ability to correctly interpret the problem.\",\n", - " \"accepted_values\": [\"completely off\", \"slightly relevant\", \"relevant\", \"mostly accurate\", \"completely accurate\"]\n", - " },\n", - " \"Mathematical Methodology\": {\n", - " \"description\": \"Adequacy of the chosen mathematical or algorithmic methodology for the question\",\n", - " \"accepted_values\": [\"inappropriate\", \"barely adequate\", \"adequate\", \"mostly effective\", \"completely effective\"]\n", - " },\n", - " \"Calculation Correctness\": {\n", - " \"description\": \"Accuracy of calculations made and solutions given\",\n", - " \"accepted_values\": [\"completely incorrect\", \"mostly incorrect\", \"neither\", \"mostly correct\", \"completely correct\"]\n", - " },\n", - " \"Explanation Clarity\": {\n", - " \"description\": \"Clarity and comprehensibility of explanations, including language use and structure\",\n", - " \"accepted_values\": [\"not at all clear\", \"slightly clear\", \"moderately clear\", \"very clear\", \"completely clear\"]\n", - " },\n", - " \"Code Efficiency\": {\n", - " \"description\": \"Quality of code in terms of efficiency and elegance\",\n", - " \"accepted_values\": [\"not at all efficient\", \"slightly efficient\", \"moderately efficient\", \"very efficient\", \"extremely efficient\"]\n", - " },\n", - " \"Code Correctness\": {\n", - " \"description\": \"Correctness of the provided code\",\n", - " \"accepted_values\": [\"completely incorrect\", \"mostly incorrect\", \"partly correct\", \"mostly correct\", \"completely correct\"]\n", - " }\n", + " Task description: Given any question, the system needs to solve the problem as consisely and accurately as possible\n", + " Task successful example: {'problem': 'What is the sum of all the distinct positive two-digit factors of 144?', 'level': 'Level 5', 'type': 'Number Theory', 'solution': 'Prime factorize $144=2^4\\\\cdot3^2$. The sum of the positive two-digit factors of 144 is $2^4+2\\\\cdot3^2+2^2\\\\cdot3+2^2\\\\cdot3^2+2^3\\\\cdot3+2^3\\\\cdot3^2+2^4\\\\cdot3=\\\\boxed{226}.$', 'problem_id': '0', 'response_with_ans': 'To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere\\'s a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\"The sum of all the distinct positive two-digit factors of 144 is:\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.', 'round': 0, 'messages': [{'content': 'What is the sum of all the distinct positive two-digit factors of 144?', 'role': 'user'}, {'content': 'To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere\\'s a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\"The sum of all the distinct positive two-digit factors of 144 is:\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.', 'role': 'assistant'}], 'time': 11.140539407730103, 'trial': -1}\n", + " Task failed example: {'problem': 'Find all $x$ that satisfy the inequality $(2x+10)(x+3)<(3x+9)(x+8)$. Express your answer in interval notation.', 'level': 'Level 5', 'type': 'Algebra', 'solution': 'We have \\\\begin{align*} (2x+10)(x+3)&<(3x+9)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)&<3(x+3)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)-3(x+3)(x+8)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (2x+10-(3x+24))(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (-x-14)(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (x+14)(x+3)&>0.\\n\\\\end{align*} This inequality is satisfied if and only if $(x+14)$ and $(x+3)$ are either both positive or both negative. Both factors are positive for $x>-3$ and both factors are negative for $x<-14$. When $-14 0$ and then find intervals where the inequality holds true.\\n\\nLet's first find the factorized form of the quadratic:\", 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': \"I apologize for the confusion in my previous message. Let's find the factorized form of the quadratic using Python code.\\n\\n```python\\nfrom sympy import symbols, factor\\r\\nx = symbols('x')\\r\\nfactorized = factor(x**2 + 17*x + 42)\\r\\nfactorized\\r\\n```\\n\\nPlease run the Python code above to find the factorized form of the quadratic, and we can proceed to determine the intervals where the inequality holds true.\", 'role': 'assistant'}], 'time': 24.91333508491516, 'trial': -1}\n", + " Evaluation dictionary: [\n", + " {\n", + " \"name\": \"Problem Interpretation\",\n", + " \"description\": \"Ability to correctly interpret the problem.\",\n", + " \"accepted_values\": [\n", + " \"completely off\",\n", + " \"slightly relevant\",\n", + " \"relevant\",\n", + " \"mostly accurate\",\n", + " \"completely accurate\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Mathematical Methodology\",\n", + " \"description\": \"Adequacy of the chosen mathematical or algorithmic methodology for the question\",\n", + " \"accepted_values\": [\n", + " \"inappropriate\",\n", + " \"barely adequate\",\n", + " \"adequate\",\n", + " \"mostly effective\",\n", + " \"completely effective\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Calculation Correctness\",\n", + " \"description\": \"Accuracy of calculations made and solutions given\",\n", + " \"accepted_values\": [\n", + " \"completely incorrect\",\n", + " \"mostly incorrect\",\n", + " \"neither\",\n", + " \"mostly correct\",\n", + " \"completely correct\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Explanation Clarity\",\n", + " \"description\": \"Clarity and comprehensibility of explanations, including language use and structure\",\n", + " \"accepted_values\": [\n", + " \"not at all clear\",\n", + " \"slightly clear\",\n", + " \"moderately clear\",\n", + " \"very clear\",\n", + " \"completely clear\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Code Efficiency\",\n", + " \"description\": \"Quality of code in terms of efficiency and elegance\",\n", + " \"accepted_values\": [\n", + " \"not at all efficient\",\n", + " \"slightly efficient\",\n", + " \"moderately efficient\",\n", + " \"very efficient\",\n", + " \"extremely efficient\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Code Correctness\",\n", + " \"description\": \"Correctness of the provided code\",\n", + " \"accepted_values\": [\n", + " \"completely incorrect\",\n", + " \"mostly incorrect\",\n", + " \"partly correct\",\n", + " \"mostly correct\",\n", + " \"completely correct\"\n", + " ],\n", + " \"sub_criteria\": []\n", " }\n", - "actual test case to evaluate: {\n", - " \"problem\": \"On a number line, the coordinates of $P$ and $Q$ are 8 and 48, respectively. The midpoint of $\\\\overline{PQ}$ is $B$, the midpoint of $\\\\overline{BQ}$ is $C$, and the midpoint of $\\\\overline{PC}$ is $D$. What is the coordinate of $D$?\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Prealgebra\",\n", - " \"solution\": \"Since $B$ is midpoint of $\\\\overline{PQ}$, the coordinate of $B$ is $(8+48)/2 = 4+24 = 28$. Since $C$ is the midpoint of $\\\\overline{BQ}$, the coordinate of $C$ is $(28+48)/2 = 14+24=38$. Since $D$ is the midpoint of $\\\\overline{PC}$, the coordinate of $D$ is $(8+38)/2 = 4 + 19 = \\\\boxed{23}$.\\n\\n[asy]\\npair P, Q, B, C, D;\\n\\nP = (8,0);\\nQ = (48,0);\\nB = (P+Q)/2;\\nC = (B+Q)/2;\\nD = (P+C)/2;\\n\\ndot(P);\\ndot(Q);\\ndot(B);\\ndot(C);\\ndot(D);\\n\\ndraw(P--Q);\\nlabel(\\\"$P$\\\",P,S);\\nlabel(\\\"$Q$\\\",Q,S);\\nlabel(\\\"$B$\\\",B,S);\\nlabel(\\\"$C$\\\",C,S);\\nlabel(\\\"$D$\\\",D,S);\\n[/asy]\",\n", - " \"problem_id\": \"6\",\n", - " \"response_with_ans\": \"Let's find the coordinates using Python code.\\n\\n```python\\nP = 8\\nQ = 48\\n\\n# Find the midpoint of PQ (B)\\nB = (P + Q) / 2\\n\\n# Find the midpoint of BQ (C)\\nC = (B + Q) / 2\\n\\n# Find the midpoint of PC (D)\\nD = (P + C) / 2\\n\\nD\\n```\\n\\nExecute the above Python code to find the coordinate of D.\",\n", - " \"round\": 1,\n", - " \"messages\": [\n", - " {\n", - " \"content\": \"On a number line, the coordinates of $P$ and $Q$ are 8 and 48, respectively. The midpoint of $\\\\overline{PQ}$ is $B$, the midpoint of $\\\\overline{BQ}$ is $C$, and the midpoint of $\\\\overline{PC}$ is $D$. What is the coordinate of $D$?\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"To find the coordinate of D, we need to follow these steps:\\n1. Find the coordinate of B (midpoint of PQ)\\n2. Find the coordinate of C (midpoint of BQ)\\n3. Find the coordinate of D (midpoint of PC)\\n\\nI'll use Python code to find the coordinates of B, C, and D.\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"Let's find the coordinates using Python code.\\n\\n```python\\nP = 8\\nQ = 48\\n\\n# Find the midpoint of PQ (B)\\nB = (P + Q) / 2\\n\\n# Find the midpoint of BQ (C)\\nC = (B + Q) / 2\\n\\n# Find the midpoint of PC (D)\\nD = (P + C) / 2\\n\\nD\\n```\\n\\nExecute the above Python code to find the coordinate of D.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 9.041668176651001,\n", - " \"trial\": -1\n", - "}\n", + "]actual test case to evaluate: {'problem': 'On a number line, the coordinates of $P$ and $Q$ are 8 and 48, respectively. The midpoint of $\\\\overline{PQ}$ is $B$, the midpoint of $\\\\overline{BQ}$ is $C$, and the midpoint of $\\\\overline{PC}$ is $D$. What is the coordinate of $D$?', 'level': 'Level 5', 'type': 'Prealgebra', 'solution': 'Since $B$ is midpoint of $\\\\overline{PQ}$, the coordinate of $B$ is $(8+48)/2 = 4+24 = 28$. Since $C$ is the midpoint of $\\\\overline{BQ}$, the coordinate of $C$ is $(28+48)/2 = 14+24=38$. Since $D$ is the midpoint of $\\\\overline{PC}$, the coordinate of $D$ is $(8+38)/2 = 4 + 19 = \\\\boxed{23}$.\\n\\n[asy]\\npair P, Q, B, C, D;\\n\\nP = (8,0);\\nQ = (48,0);\\nB = (P+Q)/2;\\nC = (B+Q)/2;\\nD = (P+C)/2;\\n\\ndot(P);\\ndot(Q);\\ndot(B);\\ndot(C);\\ndot(D);\\n\\ndraw(P--Q);\\nlabel(\"$P$\",P,S);\\nlabel(\"$Q$\",Q,S);\\nlabel(\"$B$\",B,S);\\nlabel(\"$C$\",C,S);\\nlabel(\"$D$\",D,S);\\n[/asy]', 'problem_id': '6', 'response_with_ans': \"Let's find the coordinates using Python code.\\n\\n```python\\nP = 8\\nQ = 48\\n\\n# Find the midpoint of PQ (B)\\nB = (P + Q) / 2\\n\\n# Find the midpoint of BQ (C)\\nC = (B + Q) / 2\\n\\n# Find the midpoint of PC (D)\\nD = (P + C) / 2\\n\\nD\\n```\\n\\nExecute the above Python code to find the coordinate of D.\", 'round': 1, 'messages': [{'content': 'On a number line, the coordinates of $P$ and $Q$ are 8 and 48, respectively. The midpoint of $\\\\overline{PQ}$ is $B$, the midpoint of $\\\\overline{BQ}$ is $C$, and the midpoint of $\\\\overline{PC}$ is $D$. What is the coordinate of $D$?', 'role': 'user'}, {'content': \"To find the coordinate of D, we need to follow these steps:\\n1. Find the coordinate of B (midpoint of PQ)\\n2. Find the coordinate of C (midpoint of BQ)\\n3. Find the coordinate of D (midpoint of PC)\\n\\nI'll use Python code to find the coordinates of B, C, and D.\", 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': \"Let's find the coordinates using Python code.\\n\\n```python\\nP = 8\\nQ = 48\\n\\n# Find the midpoint of PQ (B)\\nB = (P + Q) / 2\\n\\n# Find the midpoint of BQ (C)\\nC = (B + Q) / 2\\n\\n# Find the midpoint of PC (D)\\nD = (P + C) / 2\\n\\nD\\n```\\n\\nExecute the above Python code to find the coordinate of D.\", 'role': 'assistant'}], 'time': 9.041668176651001, 'trial': -1}\n", "\n", "--------------------------------------------------------------------------------\n", + "\u001b[31m\n", + ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", "\u001b[33mquantifier\u001b[0m (to quantifier_user):\n", "\n", + "```json\n", "{\n", " \"Problem Interpretation\": \"completely accurate\",\n", " \"Mathematical Methodology\": \"completely effective\",\n", " \"Calculation Correctness\": \"completely correct\",\n", " \"Explanation Clarity\": \"very clear\",\n", - " \"Code Efficiency\": \"very efficient\",\n", + " \"Code Efficiency\": \"moderately efficient\",\n", " \"Code Correctness\": \"completely correct\"\n", "}\n", + "```\n", "\n", "--------------------------------------------------------------------------------\n", - "actual label for this case: true\n", "\u001b[33mquantifier_user\u001b[0m (to quantifier):\n", "\n", "Task: Math problem solving.\n", - "Task description: Given any question, the system needs to solve the problem as consisely and accurately as possible\n", - "Task successful example: {\n", - " \"problem\": \"What is the sum of all the distinct positive two-digit factors of 144?\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Number Theory\",\n", - " \"solution\": \"Prime factorize $144=2^4\\\\cdot3^2$. The sum of the positive two-digit factors of 144 is $2^4+2\\\\cdot3^2+2^2\\\\cdot3+2^2\\\\cdot3^2+2^3\\\\cdot3+2^3\\\\cdot3^2+2^4\\\\cdot3=\\\\boxed{226}.$\",\n", - " \"problem_id\": \"0\",\n", - " \"response_with_ans\": \"To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere's a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\\\"The sum of all the distinct positive two-digit factors of 144 is:\\\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.\",\n", - " \"round\": 0,\n", - " \"messages\": [\n", - " {\n", - " \"content\": \"What is the sum of all the distinct positive two-digit factors of 144?\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere's a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\\\"The sum of all the distinct positive two-digit factors of 144 is:\\\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 11.140539407730103,\n", - " \"trial\": -1\n", - "}\n", - "\n", - "Task failed example: {\n", - " \"problem\": \"Find all $x$ that satisfy the inequality $(2x+10)(x+3)<(3x+9)(x+8)$. Express your answer in interval notation.\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Algebra\",\n", - " \"solution\": \"We have \\\\begin{align*} (2x+10)(x+3)&<(3x+9)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)&<3(x+3)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)-3(x+3)(x+8)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (2x+10-(3x+24))(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (-x-14)(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (x+14)(x+3)&>0.\\n\\\\end{align*} This inequality is satisfied if and only if $(x+14)$ and $(x+3)$ are either both positive or both negative. Both factors are positive for $x>-3$ and both factors are negative for $x<-14$. When $-14 0$ and then find intervals where the inequality holds true.\\n\\nLet's first find the factorized form of the quadratic:\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"I apologize for the confusion in my previous message. Let's find the factorized form of the quadratic using Python code.\\n\\n```python\\nfrom sympy import symbols, factor\\r\\nx = symbols('x')\\r\\nfactorized = factor(x**2 + 17*x + 42)\\r\\nfactorized\\r\\n```\\n\\nPlease run the Python code above to find the factorized form of the quadratic, and we can proceed to determine the intervals where the inequality holds true.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 24.91333508491516,\n", - " \"trial\": -1\n", - "}\n", - "\n", - "Evaluation dictionary: {\n", - " \"Problem Interpretation\": {\n", - " \"description\": \"Ability to correctly interpret the problem.\",\n", - " \"accepted_values\": [\"completely off\", \"slightly relevant\", \"relevant\", \"mostly accurate\", \"completely accurate\"]\n", - " },\n", - " \"Mathematical Methodology\": {\n", - " \"description\": \"Adequacy of the chosen mathematical or algorithmic methodology for the question\",\n", - " \"accepted_values\": [\"inappropriate\", \"barely adequate\", \"adequate\", \"mostly effective\", \"completely effective\"]\n", - " },\n", - " \"Calculation Correctness\": {\n", - " \"description\": \"Accuracy of calculations made and solutions given\",\n", - " \"accepted_values\": [\"completely incorrect\", \"mostly incorrect\", \"neither\", \"mostly correct\", \"completely correct\"]\n", - " },\n", - " \"Explanation Clarity\": {\n", - " \"description\": \"Clarity and comprehensibility of explanations, including language use and structure\",\n", - " \"accepted_values\": [\"not at all clear\", \"slightly clear\", \"moderately clear\", \"very clear\", \"completely clear\"]\n", - " },\n", - " \"Code Efficiency\": {\n", - " \"description\": \"Quality of code in terms of efficiency and elegance\",\n", - " \"accepted_values\": [\"not at all efficient\", \"slightly efficient\", \"moderately efficient\", \"very efficient\", \"extremely efficient\"]\n", - " },\n", - " \"Code Correctness\": {\n", - " \"description\": \"Correctness of the provided code\",\n", - " \"accepted_values\": [\"completely incorrect\", \"mostly incorrect\", \"partly correct\", \"mostly correct\", \"completely correct\"]\n", - " }\n", + " Task description: Given any question, the system needs to solve the problem as consisely and accurately as possible\n", + " Task successful example: {'problem': 'What is the sum of all the distinct positive two-digit factors of 144?', 'level': 'Level 5', 'type': 'Number Theory', 'solution': 'Prime factorize $144=2^4\\\\cdot3^2$. The sum of the positive two-digit factors of 144 is $2^4+2\\\\cdot3^2+2^2\\\\cdot3+2^2\\\\cdot3^2+2^3\\\\cdot3+2^3\\\\cdot3^2+2^4\\\\cdot3=\\\\boxed{226}.$', 'problem_id': '0', 'response_with_ans': 'To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere\\'s a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\"The sum of all the distinct positive two-digit factors of 144 is:\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.', 'round': 0, 'messages': [{'content': 'What is the sum of all the distinct positive two-digit factors of 144?', 'role': 'user'}, {'content': 'To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere\\'s a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\"The sum of all the distinct positive two-digit factors of 144 is:\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.', 'role': 'assistant'}], 'time': 11.140539407730103, 'trial': -1}\n", + " Task failed example: {'problem': 'Find all $x$ that satisfy the inequality $(2x+10)(x+3)<(3x+9)(x+8)$. Express your answer in interval notation.', 'level': 'Level 5', 'type': 'Algebra', 'solution': 'We have \\\\begin{align*} (2x+10)(x+3)&<(3x+9)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)&<3(x+3)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)-3(x+3)(x+8)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (2x+10-(3x+24))(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (-x-14)(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (x+14)(x+3)&>0.\\n\\\\end{align*} This inequality is satisfied if and only if $(x+14)$ and $(x+3)$ are either both positive or both negative. Both factors are positive for $x>-3$ and both factors are negative for $x<-14$. When $-14 0$ and then find intervals where the inequality holds true.\\n\\nLet's first find the factorized form of the quadratic:\", 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': \"I apologize for the confusion in my previous message. Let's find the factorized form of the quadratic using Python code.\\n\\n```python\\nfrom sympy import symbols, factor\\r\\nx = symbols('x')\\r\\nfactorized = factor(x**2 + 17*x + 42)\\r\\nfactorized\\r\\n```\\n\\nPlease run the Python code above to find the factorized form of the quadratic, and we can proceed to determine the intervals where the inequality holds true.\", 'role': 'assistant'}], 'time': 24.91333508491516, 'trial': -1}\n", + " Evaluation dictionary: [\n", + " {\n", + " \"name\": \"Problem Interpretation\",\n", + " \"description\": \"Ability to correctly interpret the problem.\",\n", + " \"accepted_values\": [\n", + " \"completely off\",\n", + " \"slightly relevant\",\n", + " \"relevant\",\n", + " \"mostly accurate\",\n", + " \"completely accurate\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Mathematical Methodology\",\n", + " \"description\": \"Adequacy of the chosen mathematical or algorithmic methodology for the question\",\n", + " \"accepted_values\": [\n", + " \"inappropriate\",\n", + " \"barely adequate\",\n", + " \"adequate\",\n", + " \"mostly effective\",\n", + " \"completely effective\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Calculation Correctness\",\n", + " \"description\": \"Accuracy of calculations made and solutions given\",\n", + " \"accepted_values\": [\n", + " \"completely incorrect\",\n", + " \"mostly incorrect\",\n", + " \"neither\",\n", + " \"mostly correct\",\n", + " \"completely correct\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Explanation Clarity\",\n", + " \"description\": \"Clarity and comprehensibility of explanations, including language use and structure\",\n", + " \"accepted_values\": [\n", + " \"not at all clear\",\n", + " \"slightly clear\",\n", + " \"moderately clear\",\n", + " \"very clear\",\n", + " \"completely clear\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Code Efficiency\",\n", + " \"description\": \"Quality of code in terms of efficiency and elegance\",\n", + " \"accepted_values\": [\n", + " \"not at all efficient\",\n", + " \"slightly efficient\",\n", + " \"moderately efficient\",\n", + " \"very efficient\",\n", + " \"extremely efficient\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Code Correctness\",\n", + " \"description\": \"Correctness of the provided code\",\n", + " \"accepted_values\": [\n", + " \"completely incorrect\",\n", + " \"mostly incorrect\",\n", + " \"partly correct\",\n", + " \"mostly correct\",\n", + " \"completely correct\"\n", + " ],\n", + " \"sub_criteria\": []\n", " }\n", - "actual test case to evaluate: {\n", - " \"problem\": \"Triangle $ABC$ is a right triangle. If the measure of angle $PAB$ is $x^\\\\circ$ and the measure of angle $ACB$ is expressed in the form $(Mx+N)^\\\\circ$ with $M=1$, what is the value of $M+N$?\\n\\n[asy]\\ndraw((-10,0)--(20,0),linewidth(1),Arrows);\\ndraw((0,0)--(10,10/sqrt(3))--(10+10/3,0),linewidth(1));\\n\\ndraw((10,10/sqrt(3))+dir(-150)--(10,10/sqrt(3))+dir(-150)+dir(-60)--(10,10/sqrt(3))+dir(-60),linewidth(1));\\n\\ndot((-3,0));\\n\\ndraw(dir(180)..dir(105)..dir(30),linewidth(1));\\n\\nlabel(\\\"P\\\",(-3,0),NW);\\nlabel(\\\"A\\\",(0,0),S);\\nlabel(\\\"$x^\\\\circ$\\\",(-1,1),N);\\nlabel(\\\"B\\\",(10,10/sqrt(3)),N);\\nlabel(\\\"C\\\",(10+10/3,0),NE);\\n\\n[/asy]\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Prealgebra\",\n", - " \"solution\": \"Since $\\\\angle PAB$ and $\\\\angle BAC$ are supplementary, $\\\\angle BAC = 180^{\\\\circ} - x^\\\\circ$. Since the three angles of a triangle add up to $ 180^{\\\\circ} $, we have $\\\\angle ACB = 180^{\\\\circ} - 90^{\\\\circ} - (180^{\\\\circ} - x^\\\\circ) = x^\\\\circ - 90^{\\\\circ}$. Thus, $M + N = \\\\boxed{-89}$.\",\n", - " \"problem_id\": \"0\",\n", - " \"response_with_ans\": \"We know that $x + y = 180^\\\\circ$. From this equation, we can express $y$ in terms of $x$: $y = 180^\\\\circ - x$.\\n\\nNow we substitute the expression for $y$ in the expression of angle $ACB$:\\n$ACB = 90^\\\\circ - y = 90^\\\\circ - (180^\\\\circ - x) = x - 90^\\\\circ$.\\n\\nComparing this expression with the given form $(Mx + N)^\\\\circ$, we can see that $M = 1$ and $N = -90$. Therefore, $M + N = 1 + (-90) = -89$.\\n\\nThe value of $M + N$ is $-89$.\",\n", - " \"round\": 2,\n", - " \"messages\": [\n", - " {\n", - " \"content\": \"Triangle $ABC$ is a right triangle. If the measure of angle $PAB$ is $x^\\\\circ$ and the measure of angle $ACB$ is expressed in the form $(Mx+N)^\\\\circ$ with $M=1$, what is the value of $M+N$?\\n\\n[asy]\\ndraw((-10,0)--(20,0),linewidth(1),Arrows);\\ndraw((0,0)--(10,10/sqrt(3))--(10+10/3,0),linewidth(1));\\n\\ndraw((10,10/sqrt(3))+dir(-150)--(10,10/sqrt(3))+dir(-150)+dir(-60)--(10,10/sqrt(3))+dir(-60),linewidth(1));\\n\\ndot((-3,0));\\n\\ndraw(dir(180)..dir(105)..dir(30),linewidth(1));\\n\\nlabel(\\\"P\\\",(-3,0),NW);\\nlabel(\\\"A\\\",(0,0),S);\\nlabel(\\\"$x^\\\\circ$\\\",(-1,1),N);\\nlabel(\\\"B\\\",(10,10/sqrt(3)),N);\\nlabel(\\\"C\\\",(10+10/3,0),NE);\\n\\n[/asy]\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"To solve this problem, we need to find the relationship between angle $x$ and angle $ACB$. We know that triangle $ABC$ is a right triangle, so the sum of angles $ACB$ and $ABC$ must be $90^\\\\circ$. Let's call the angle $ABC$ as $y$. Therefore, angle $ACB = 90^\\\\circ - y$.\\n\\nNow, we need to find the relationship between angles $x$ and $y$. Since $PAB$ and $ABC$ share the same vertex $A$ and angle $PAB$ is opposite angle $ABC$, they are explementary (their sum is $180^\\\\circ$). So, we have $x + y = 180^\\\\circ$.\\n\\nNow, we can express $y$ in terms of $x$ and then substitute the expression for $y$ in the expression of angle $ACB$. Finally, we can compare angle $ACB$'s expression with the given form $(Mx + N)^\\\\circ$ to find the values of $M$ and $N$.\\n\\nLet me do the calculations.\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"We know that $x + y = 180^\\\\circ$. From this equation, we can express $y$ in terms of $x$: $y = 180^\\\\circ - x$.\\n\\nNow we substitute the expression for $y$ in the expression of angle $ACB$:\\n$ACB = 90^\\\\circ - y = 90^\\\\circ - (180^\\\\circ - x) = x - 90^\\\\circ$.\\n\\nComparing this expression with the given form $(Mx + N)^\\\\circ$, we can see that $M = 1$ and $N = -90$. Therefore, $M + N = 1 + (-90) = -89$.\\n\\nThe value of $M + N$ is $-89$.\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"TERMINATE\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 28.305670976638794,\n", - " \"trial\": -1\n", - "}\n", + "]actual test case to evaluate: {'problem': 'Triangle $ABC$ is a right triangle. If the measure of angle $PAB$ is $x^\\\\circ$ and the measure of angle $ACB$ is expressed in the form $(Mx+N)^\\\\circ$ with $M=1$, what is the value of $M+N$?\\n\\n[asy]\\ndraw((-10,0)--(20,0),linewidth(1),Arrows);\\ndraw((0,0)--(10,10/sqrt(3))--(10+10/3,0),linewidth(1));\\n\\ndraw((10,10/sqrt(3))+dir(-150)--(10,10/sqrt(3))+dir(-150)+dir(-60)--(10,10/sqrt(3))+dir(-60),linewidth(1));\\n\\ndot((-3,0));\\n\\ndraw(dir(180)..dir(105)..dir(30),linewidth(1));\\n\\nlabel(\"P\",(-3,0),NW);\\nlabel(\"A\",(0,0),S);\\nlabel(\"$x^\\\\circ$\",(-1,1),N);\\nlabel(\"B\",(10,10/sqrt(3)),N);\\nlabel(\"C\",(10+10/3,0),NE);\\n\\n[/asy]', 'level': 'Level 5', 'type': 'Prealgebra', 'solution': 'Since $\\\\angle PAB$ and $\\\\angle BAC$ are supplementary, $\\\\angle BAC = 180^{\\\\circ} - x^\\\\circ$. Since the three angles of a triangle add up to $ 180^{\\\\circ} $, we have $\\\\angle ACB = 180^{\\\\circ} - 90^{\\\\circ} - (180^{\\\\circ} - x^\\\\circ) = x^\\\\circ - 90^{\\\\circ}$. Thus, $M + N = \\\\boxed{-89}$.', 'problem_id': '0', 'response_with_ans': 'We know that $x + y = 180^\\\\circ$. From this equation, we can express $y$ in terms of $x$: $y = 180^\\\\circ - x$.\\n\\nNow we substitute the expression for $y$ in the expression of angle $ACB$:\\n$ACB = 90^\\\\circ - y = 90^\\\\circ - (180^\\\\circ - x) = x - 90^\\\\circ$.\\n\\nComparing this expression with the given form $(Mx + N)^\\\\circ$, we can see that $M = 1$ and $N = -90$. Therefore, $M + N = 1 + (-90) = -89$.\\n\\nThe value of $M + N$ is $-89$.', 'round': 2, 'messages': [{'content': 'Triangle $ABC$ is a right triangle. If the measure of angle $PAB$ is $x^\\\\circ$ and the measure of angle $ACB$ is expressed in the form $(Mx+N)^\\\\circ$ with $M=1$, what is the value of $M+N$?\\n\\n[asy]\\ndraw((-10,0)--(20,0),linewidth(1),Arrows);\\ndraw((0,0)--(10,10/sqrt(3))--(10+10/3,0),linewidth(1));\\n\\ndraw((10,10/sqrt(3))+dir(-150)--(10,10/sqrt(3))+dir(-150)+dir(-60)--(10,10/sqrt(3))+dir(-60),linewidth(1));\\n\\ndot((-3,0));\\n\\ndraw(dir(180)..dir(105)..dir(30),linewidth(1));\\n\\nlabel(\"P\",(-3,0),NW);\\nlabel(\"A\",(0,0),S);\\nlabel(\"$x^\\\\circ$\",(-1,1),N);\\nlabel(\"B\",(10,10/sqrt(3)),N);\\nlabel(\"C\",(10+10/3,0),NE);\\n\\n[/asy]', 'role': 'user'}, {'content': \"To solve this problem, we need to find the relationship between angle $x$ and angle $ACB$. We know that triangle $ABC$ is a right triangle, so the sum of angles $ACB$ and $ABC$ must be $90^\\\\circ$. Let's call the angle $ABC$ as $y$. Therefore, angle $ACB = 90^\\\\circ - y$.\\n\\nNow, we need to find the relationship between angles $x$ and $y$. Since $PAB$ and $ABC$ share the same vertex $A$ and angle $PAB$ is opposite angle $ABC$, they are explementary (their sum is $180^\\\\circ$). So, we have $x + y = 180^\\\\circ$.\\n\\nNow, we can express $y$ in terms of $x$ and then substitute the expression for $y$ in the expression of angle $ACB$. Finally, we can compare angle $ACB$'s expression with the given form $(Mx + N)^\\\\circ$ to find the values of $M$ and $N$.\\n\\nLet me do the calculations.\", 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': 'We know that $x + y = 180^\\\\circ$. From this equation, we can express $y$ in terms of $x$: $y = 180^\\\\circ - x$.\\n\\nNow we substitute the expression for $y$ in the expression of angle $ACB$:\\n$ACB = 90^\\\\circ - y = 90^\\\\circ - (180^\\\\circ - x) = x - 90^\\\\circ$.\\n\\nComparing this expression with the given form $(Mx + N)^\\\\circ$, we can see that $M = 1$ and $N = -90$. Therefore, $M + N = 1 + (-90) = -89$.\\n\\nThe value of $M + N$ is $-89$.', 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': 'TERMINATE', 'role': 'assistant'}], 'time': 28.305670976638794, 'trial': -1}\n", "\n", "--------------------------------------------------------------------------------\n", + "\u001b[31m\n", + ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", "\u001b[33mquantifier\u001b[0m (to quantifier_user):\n", "\n", "{\n", - " \"Problem Interpretation\": \"completely accurate\",\n", - " \"Mathematical Methodology\": \"completely effective\",\n", - " \"Calculation Correctness\": \"completely correct\",\n", - " \"Explanation Clarity\": \"very clear\",\n", - " \"Code Efficiency\": \"not at all efficient\",\n", - " \"Code Correctness\": \"completely correct\"\n", + " \"Problem Interpretation\": \"completely accurate\",\n", + " \"Mathematical Methodology\": \"completely effective\",\n", + " \"Calculation Correctness\": \"completely correct\",\n", + " \"Explanation Clarity\": \"very clear\",\n", + " \"Code Efficiency\": \"not applicable\",\n", + " \"Code Correctness\": \"not applicable\"\n", "}\n", "\n", "--------------------------------------------------------------------------------\n", - "actual label for this case: true\n", "\u001b[33mquantifier_user\u001b[0m (to quantifier):\n", "\n", "Task: Math problem solving.\n", - "Task description: Given any question, the system needs to solve the problem as consisely and accurately as possible\n", - "Task successful example: {\n", - " \"problem\": \"What is the sum of all the distinct positive two-digit factors of 144?\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Number Theory\",\n", - " \"solution\": \"Prime factorize $144=2^4\\\\cdot3^2$. The sum of the positive two-digit factors of 144 is $2^4+2\\\\cdot3^2+2^2\\\\cdot3+2^2\\\\cdot3^2+2^3\\\\cdot3+2^3\\\\cdot3^2+2^4\\\\cdot3=\\\\boxed{226}.$\",\n", - " \"problem_id\": \"0\",\n", - " \"response_with_ans\": \"To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere's a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\\\"The sum of all the distinct positive two-digit factors of 144 is:\\\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.\",\n", - " \"round\": 0,\n", - " \"messages\": [\n", - " {\n", - " \"content\": \"What is the sum of all the distinct positive two-digit factors of 144?\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere's a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\\\"The sum of all the distinct positive two-digit factors of 144 is:\\\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 11.140539407730103,\n", - " \"trial\": -1\n", - "}\n", - "\n", - "Task failed example: {\n", - " \"problem\": \"Find all $x$ that satisfy the inequality $(2x+10)(x+3)<(3x+9)(x+8)$. Express your answer in interval notation.\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Algebra\",\n", - " \"solution\": \"We have \\\\begin{align*} (2x+10)(x+3)&<(3x+9)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)&<3(x+3)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)-3(x+3)(x+8)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (2x+10-(3x+24))(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (-x-14)(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (x+14)(x+3)&>0.\\n\\\\end{align*} This inequality is satisfied if and only if $(x+14)$ and $(x+3)$ are either both positive or both negative. Both factors are positive for $x>-3$ and both factors are negative for $x<-14$. When $-14 0$ and then find intervals where the inequality holds true.\\n\\nLet's first find the factorized form of the quadratic:\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"I apologize for the confusion in my previous message. Let's find the factorized form of the quadratic using Python code.\\n\\n```python\\nfrom sympy import symbols, factor\\r\\nx = symbols('x')\\r\\nfactorized = factor(x**2 + 17*x + 42)\\r\\nfactorized\\r\\n```\\n\\nPlease run the Python code above to find the factorized form of the quadratic, and we can proceed to determine the intervals where the inequality holds true.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 24.91333508491516,\n", - " \"trial\": -1\n", - "}\n", - "\n", - "Evaluation dictionary: {\n", - " \"Problem Interpretation\": {\n", - " \"description\": \"Ability to correctly interpret the problem.\",\n", - " \"accepted_values\": [\"completely off\", \"slightly relevant\", \"relevant\", \"mostly accurate\", \"completely accurate\"]\n", - " },\n", - " \"Mathematical Methodology\": {\n", - " \"description\": \"Adequacy of the chosen mathematical or algorithmic methodology for the question\",\n", - " \"accepted_values\": [\"inappropriate\", \"barely adequate\", \"adequate\", \"mostly effective\", \"completely effective\"]\n", - " },\n", - " \"Calculation Correctness\": {\n", - " \"description\": \"Accuracy of calculations made and solutions given\",\n", - " \"accepted_values\": [\"completely incorrect\", \"mostly incorrect\", \"neither\", \"mostly correct\", \"completely correct\"]\n", - " },\n", - " \"Explanation Clarity\": {\n", - " \"description\": \"Clarity and comprehensibility of explanations, including language use and structure\",\n", - " \"accepted_values\": [\"not at all clear\", \"slightly clear\", \"moderately clear\", \"very clear\", \"completely clear\"]\n", - " },\n", - " \"Code Efficiency\": {\n", - " \"description\": \"Quality of code in terms of efficiency and elegance\",\n", - " \"accepted_values\": [\"not at all efficient\", \"slightly efficient\", \"moderately efficient\", \"very efficient\", \"extremely efficient\"]\n", - " },\n", - " \"Code Correctness\": {\n", - " \"description\": \"Correctness of the provided code\",\n", - " \"accepted_values\": [\"completely incorrect\", \"mostly incorrect\", \"partly correct\", \"mostly correct\", \"completely correct\"]\n", - " }\n", + " Task description: Given any question, the system needs to solve the problem as consisely and accurately as possible\n", + " Task successful example: {'problem': 'What is the sum of all the distinct positive two-digit factors of 144?', 'level': 'Level 5', 'type': 'Number Theory', 'solution': 'Prime factorize $144=2^4\\\\cdot3^2$. The sum of the positive two-digit factors of 144 is $2^4+2\\\\cdot3^2+2^2\\\\cdot3+2^2\\\\cdot3^2+2^3\\\\cdot3+2^3\\\\cdot3^2+2^4\\\\cdot3=\\\\boxed{226}.$', 'problem_id': '0', 'response_with_ans': 'To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere\\'s a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\"The sum of all the distinct positive two-digit factors of 144 is:\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.', 'round': 0, 'messages': [{'content': 'What is the sum of all the distinct positive two-digit factors of 144?', 'role': 'user'}, {'content': 'To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere\\'s a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\"The sum of all the distinct positive two-digit factors of 144 is:\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.', 'role': 'assistant'}], 'time': 11.140539407730103, 'trial': -1}\n", + " Task failed example: {'problem': 'Find all $x$ that satisfy the inequality $(2x+10)(x+3)<(3x+9)(x+8)$. Express your answer in interval notation.', 'level': 'Level 5', 'type': 'Algebra', 'solution': 'We have \\\\begin{align*} (2x+10)(x+3)&<(3x+9)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)&<3(x+3)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)-3(x+3)(x+8)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (2x+10-(3x+24))(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (-x-14)(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (x+14)(x+3)&>0.\\n\\\\end{align*} This inequality is satisfied if and only if $(x+14)$ and $(x+3)$ are either both positive or both negative. Both factors are positive for $x>-3$ and both factors are negative for $x<-14$. When $-14 0$ and then find intervals where the inequality holds true.\\n\\nLet's first find the factorized form of the quadratic:\", 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': \"I apologize for the confusion in my previous message. Let's find the factorized form of the quadratic using Python code.\\n\\n```python\\nfrom sympy import symbols, factor\\r\\nx = symbols('x')\\r\\nfactorized = factor(x**2 + 17*x + 42)\\r\\nfactorized\\r\\n```\\n\\nPlease run the Python code above to find the factorized form of the quadratic, and we can proceed to determine the intervals where the inequality holds true.\", 'role': 'assistant'}], 'time': 24.91333508491516, 'trial': -1}\n", + " Evaluation dictionary: [\n", + " {\n", + " \"name\": \"Problem Interpretation\",\n", + " \"description\": \"Ability to correctly interpret the problem.\",\n", + " \"accepted_values\": [\n", + " \"completely off\",\n", + " \"slightly relevant\",\n", + " \"relevant\",\n", + " \"mostly accurate\",\n", + " \"completely accurate\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Mathematical Methodology\",\n", + " \"description\": \"Adequacy of the chosen mathematical or algorithmic methodology for the question\",\n", + " \"accepted_values\": [\n", + " \"inappropriate\",\n", + " \"barely adequate\",\n", + " \"adequate\",\n", + " \"mostly effective\",\n", + " \"completely effective\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Calculation Correctness\",\n", + " \"description\": \"Accuracy of calculations made and solutions given\",\n", + " \"accepted_values\": [\n", + " \"completely incorrect\",\n", + " \"mostly incorrect\",\n", + " \"neither\",\n", + " \"mostly correct\",\n", + " \"completely correct\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Explanation Clarity\",\n", + " \"description\": \"Clarity and comprehensibility of explanations, including language use and structure\",\n", + " \"accepted_values\": [\n", + " \"not at all clear\",\n", + " \"slightly clear\",\n", + " \"moderately clear\",\n", + " \"very clear\",\n", + " \"completely clear\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Code Efficiency\",\n", + " \"description\": \"Quality of code in terms of efficiency and elegance\",\n", + " \"accepted_values\": [\n", + " \"not at all efficient\",\n", + " \"slightly efficient\",\n", + " \"moderately efficient\",\n", + " \"very efficient\",\n", + " \"extremely efficient\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Code Correctness\",\n", + " \"description\": \"Correctness of the provided code\",\n", + " \"accepted_values\": [\n", + " \"completely incorrect\",\n", + " \"mostly incorrect\",\n", + " \"partly correct\",\n", + " \"mostly correct\",\n", + " \"completely correct\"\n", + " ],\n", + " \"sub_criteria\": []\n", " }\n", - "actual test case to evaluate: {\n", - " \"problem\": \"Simplify: $\\\\frac{\\\\sqrt{2.5^2-0.7^2}}{2.7-2.5}$.\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Prealgebra\",\n", - " \"solution\": \"We have \\\\begin{align*}\\n\\\\frac{\\\\sqrt{2.5^2 - 0.7^2}}{2.7-2.5} &= \\\\frac{\\\\sqrt{6.25 - 0.49}}{2.7-2.5} = \\\\frac{\\\\sqrt{5.76}}{0.2} = \\\\frac{\\\\sqrt{576/100}}{0.2}\\\\\\\\\\n&= \\\\frac{\\\\sqrt{576}/\\\\sqrt{100}}{0.2} = \\\\frac{24/10}{0.2} = \\\\frac{2.4}{0.2} = \\\\boxed{12}.\\\\end{align*}\",\n", - " \"problem_id\": \"2\",\n", - " \"response_with_ans\": \"```python\\nimport math\\n\\nnumerator = math.sqrt(2.5 ** 2 - 0.7 ** 2)\\ndenominator = 2.7 - 2.5\\n\\nresult = numerator / denominator\\nresult\\n```\\n\",\n", - " \"round\": 1,\n", - " \"messages\": [\n", - " {\n", - " \"content\": \"Simplify: $\\\\frac{\\\\sqrt{2.5^2-0.7^2}}{2.7-2.5}$.\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"To simplify the given expression, let's break it down step by step and use Python to calculate the values:\\n\\nStep 1: Calculate the values inside the square root: $2.5^2 - 0.7^2$\\nStep 2: Take the square root of the result\\nStep 3: Calculate the denominator: $2.7 - 2.5$\\nStep 4: Divide the result of Step 2 by the result of Step 3\\n\\nNow, let's execute Python code to get the result.\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"```python\\nimport math\\n\\nnumerator = math.sqrt(2.5 ** 2 - 0.7 ** 2)\\ndenominator = 2.7 - 2.5\\n\\nresult = numerator / denominator\\nresult\\n```\\n\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 8.612567901611328,\n", - " \"trial\": -1\n", - "}\n", + "]actual test case to evaluate: {'problem': 'John counts up from 1 to 13, and then immediately counts down again to 1, and then back up to 13, and so on, alternately counting up and down: \\\\begin{align*}\\n&(1, 2, 3,4,5,6,7,8,9,10,11,12,13,\\\\\\\\\\n&\\\\qquad\\\\qquad12,11,10,9,8,7,6,5,4,3,2,1,2,3,4,\\\\ldots ).\\n\\\\end{align*} What is the $5000^{\\\\text{th}}$ integer in his list?', 'level': 'Level 5', 'type': 'Prealgebra', 'solution': 'We can treat this list as a sequence with a repetitive pattern. We see the sequence repeats itself every 24 elements (from 1 to 13 then back to 2). When 5000 is divided by 24, its remainder is 8. Therefore we see the $5000^{\\\\text{th}}$ integer is the same as the $8^{\\\\text{th}}$ integer, which is $\\\\boxed{8}$.', 'problem_id': '4', 'response_with_ans': 'We can solve this problem by finding which \"group\" the 5000th number belongs to, and then determine the number within that group. Each group is either counting up from 1 to 13 or counting down from 13 to 1. There are 13 numbers in each direction, but since 1 is counted twice (once at the end of a down-count and once at the beginning of an up-count), counting up and down would consist of 25 numbers (13 up + 12 down).\\n\\nLet\\'s first find out which group the 5000th number belongs to and how many numbers in total are in the complete up and down groups before 5000th number using code:\\n\\n```python\\nnum_group = 5000 // 25\\nremainder = 5000 % 25\\ntotal_numbers_before = num_group * 25\\nprint(\\'Number of complete groups:\\', num_group)\\nprint(\\'Remainder:\\', remainder)\\nprint(\\'Numbers before 5000th number:\\', total_numbers_before)\\n```\\n\\nAfter knowing these values, I\\'ll determine if the 5000th number is in up-count or down-count and its value.\\n', 'round': 0, 'messages': [{'content': 'John counts up from 1 to 13, and then immediately counts down again to 1, and then back up to 13, and so on, alternately counting up and down: \\\\begin{align*}\\n&(1, 2, 3,4,5,6,7,8,9,10,11,12,13,\\\\\\\\\\n&\\\\qquad\\\\qquad12,11,10,9,8,7,6,5,4,3,2,1,2,3,4,\\\\ldots ).\\n\\\\end{align*} What is the $5000^{\\\\text{th}}$ integer in his list?', 'role': 'user'}, {'content': 'We can solve this problem by finding which \"group\" the 5000th number belongs to, and then determine the number within that group. Each group is either counting up from 1 to 13 or counting down from 13 to 1. There are 13 numbers in each direction, but since 1 is counted twice (once at the end of a down-count and once at the beginning of an up-count), counting up and down would consist of 25 numbers (13 up + 12 down).\\n\\nLet\\'s first find out which group the 5000th number belongs to and how many numbers in total are in the complete up and down groups before 5000th number using code:\\n\\n```python\\nnum_group = 5000 // 25\\nremainder = 5000 % 25\\ntotal_numbers_before = num_group * 25\\nprint(\\'Number of complete groups:\\', num_group)\\nprint(\\'Remainder:\\', remainder)\\nprint(\\'Numbers before 5000th number:\\', total_numbers_before)\\n```\\n\\nAfter knowing these values, I\\'ll determine if the 5000th number is in up-count or down-count and its value.\\n', 'role': 'assistant'}], 'time': 16.342331409454346, 'trial': -1}\n", "\n", "--------------------------------------------------------------------------------\n", + "\u001b[31m\n", + ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", "\u001b[33mquantifier\u001b[0m (to quantifier_user):\n", "\n", "{\n", - " \"Problem Interpretation\": \"completely accurate\",\n", - " \"Mathematical Methodology\": \"completely effective\",\n", - " \"Calculation Correctness\": \"completely correct\",\n", - " \"Explanation Clarity\": \"very clear\",\n", - " \"Code Efficiency\": \"moderately efficient\",\n", - " \"Code Correctness\": \"completely correct\"\n", + " \"Problem Interpretation\": \"completely accurate\",\n", + " \"Mathematical Methodology\": \"mostly effective\",\n", + " \"Calculation Correctness\": \"mostly correct\",\n", + " \"Explanation Clarity\": \"very clear\",\n", + " \"Code Efficiency\": \"moderately efficient\",\n", + " \"Code Correctness\": \"mostly correct\"\n", "}\n", "\n", "--------------------------------------------------------------------------------\n", - "actual label for this case: true\n", "\u001b[33mquantifier_user\u001b[0m (to quantifier):\n", "\n", "Task: Math problem solving.\n", - "Task description: Given any question, the system needs to solve the problem as consisely and accurately as possible\n", - "Task successful example: {\n", - " \"problem\": \"What is the sum of all the distinct positive two-digit factors of 144?\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Number Theory\",\n", - " \"solution\": \"Prime factorize $144=2^4\\\\cdot3^2$. The sum of the positive two-digit factors of 144 is $2^4+2\\\\cdot3^2+2^2\\\\cdot3+2^2\\\\cdot3^2+2^3\\\\cdot3+2^3\\\\cdot3^2+2^4\\\\cdot3=\\\\boxed{226}.$\",\n", - " \"problem_id\": \"0\",\n", - " \"response_with_ans\": \"To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere's a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\\\"The sum of all the distinct positive two-digit factors of 144 is:\\\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.\",\n", - " \"round\": 0,\n", - " \"messages\": [\n", - " {\n", - " \"content\": \"What is the sum of all the distinct positive two-digit factors of 144?\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere's a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\\\"The sum of all the distinct positive two-digit factors of 144 is:\\\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 11.140539407730103,\n", - " \"trial\": -1\n", - "}\n", - "\n", - "Task failed example: {\n", - " \"problem\": \"Find all $x$ that satisfy the inequality $(2x+10)(x+3)<(3x+9)(x+8)$. Express your answer in interval notation.\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Algebra\",\n", - " \"solution\": \"We have \\\\begin{align*} (2x+10)(x+3)&<(3x+9)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)&<3(x+3)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)-3(x+3)(x+8)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (2x+10-(3x+24))(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (-x-14)(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (x+14)(x+3)&>0.\\n\\\\end{align*} This inequality is satisfied if and only if $(x+14)$ and $(x+3)$ are either both positive or both negative. Both factors are positive for $x>-3$ and both factors are negative for $x<-14$. When $-14 0$ and then find intervals where the inequality holds true.\\n\\nLet's first find the factorized form of the quadratic:\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"I apologize for the confusion in my previous message. Let's find the factorized form of the quadratic using Python code.\\n\\n```python\\nfrom sympy import symbols, factor\\r\\nx = symbols('x')\\r\\nfactorized = factor(x**2 + 17*x + 42)\\r\\nfactorized\\r\\n```\\n\\nPlease run the Python code above to find the factorized form of the quadratic, and we can proceed to determine the intervals where the inequality holds true.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 24.91333508491516,\n", - " \"trial\": -1\n", - "}\n", - "\n", - "Evaluation dictionary: {\n", - " \"Problem Interpretation\": {\n", - " \"description\": \"Ability to correctly interpret the problem.\",\n", - " \"accepted_values\": [\"completely off\", \"slightly relevant\", \"relevant\", \"mostly accurate\", \"completely accurate\"]\n", - " },\n", - " \"Mathematical Methodology\": {\n", - " \"description\": \"Adequacy of the chosen mathematical or algorithmic methodology for the question\",\n", - " \"accepted_values\": [\"inappropriate\", \"barely adequate\", \"adequate\", \"mostly effective\", \"completely effective\"]\n", - " },\n", - " \"Calculation Correctness\": {\n", - " \"description\": \"Accuracy of calculations made and solutions given\",\n", - " \"accepted_values\": [\"completely incorrect\", \"mostly incorrect\", \"neither\", \"mostly correct\", \"completely correct\"]\n", - " },\n", - " \"Explanation Clarity\": {\n", - " \"description\": \"Clarity and comprehensibility of explanations, including language use and structure\",\n", - " \"accepted_values\": [\"not at all clear\", \"slightly clear\", \"moderately clear\", \"very clear\", \"completely clear\"]\n", - " },\n", - " \"Code Efficiency\": {\n", - " \"description\": \"Quality of code in terms of efficiency and elegance\",\n", - " \"accepted_values\": [\"not at all efficient\", \"slightly efficient\", \"moderately efficient\", \"very efficient\", \"extremely efficient\"]\n", - " },\n", - " \"Code Correctness\": {\n", - " \"description\": \"Correctness of the provided code\",\n", - " \"accepted_values\": [\"completely incorrect\", \"mostly incorrect\", \"partly correct\", \"mostly correct\", \"completely correct\"]\n", - " }\n", + " Task description: Given any question, the system needs to solve the problem as consisely and accurately as possible\n", + " Task successful example: {'problem': 'What is the sum of all the distinct positive two-digit factors of 144?', 'level': 'Level 5', 'type': 'Number Theory', 'solution': 'Prime factorize $144=2^4\\\\cdot3^2$. The sum of the positive two-digit factors of 144 is $2^4+2\\\\cdot3^2+2^2\\\\cdot3+2^2\\\\cdot3^2+2^3\\\\cdot3+2^3\\\\cdot3^2+2^4\\\\cdot3=\\\\boxed{226}.$', 'problem_id': '0', 'response_with_ans': 'To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere\\'s a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\"The sum of all the distinct positive two-digit factors of 144 is:\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.', 'round': 0, 'messages': [{'content': 'What is the sum of all the distinct positive two-digit factors of 144?', 'role': 'user'}, {'content': 'To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere\\'s a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\"The sum of all the distinct positive two-digit factors of 144 is:\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.', 'role': 'assistant'}], 'time': 11.140539407730103, 'trial': -1}\n", + " Task failed example: {'problem': 'Find all $x$ that satisfy the inequality $(2x+10)(x+3)<(3x+9)(x+8)$. Express your answer in interval notation.', 'level': 'Level 5', 'type': 'Algebra', 'solution': 'We have \\\\begin{align*} (2x+10)(x+3)&<(3x+9)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)&<3(x+3)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)-3(x+3)(x+8)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (2x+10-(3x+24))(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (-x-14)(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (x+14)(x+3)&>0.\\n\\\\end{align*} This inequality is satisfied if and only if $(x+14)$ and $(x+3)$ are either both positive or both negative. Both factors are positive for $x>-3$ and both factors are negative for $x<-14$. When $-14 0$ and then find intervals where the inequality holds true.\\n\\nLet's first find the factorized form of the quadratic:\", 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': \"I apologize for the confusion in my previous message. Let's find the factorized form of the quadratic using Python code.\\n\\n```python\\nfrom sympy import symbols, factor\\r\\nx = symbols('x')\\r\\nfactorized = factor(x**2 + 17*x + 42)\\r\\nfactorized\\r\\n```\\n\\nPlease run the Python code above to find the factorized form of the quadratic, and we can proceed to determine the intervals where the inequality holds true.\", 'role': 'assistant'}], 'time': 24.91333508491516, 'trial': -1}\n", + " Evaluation dictionary: [\n", + " {\n", + " \"name\": \"Problem Interpretation\",\n", + " \"description\": \"Ability to correctly interpret the problem.\",\n", + " \"accepted_values\": [\n", + " \"completely off\",\n", + " \"slightly relevant\",\n", + " \"relevant\",\n", + " \"mostly accurate\",\n", + " \"completely accurate\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Mathematical Methodology\",\n", + " \"description\": \"Adequacy of the chosen mathematical or algorithmic methodology for the question\",\n", + " \"accepted_values\": [\n", + " \"inappropriate\",\n", + " \"barely adequate\",\n", + " \"adequate\",\n", + " \"mostly effective\",\n", + " \"completely effective\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Calculation Correctness\",\n", + " \"description\": \"Accuracy of calculations made and solutions given\",\n", + " \"accepted_values\": [\n", + " \"completely incorrect\",\n", + " \"mostly incorrect\",\n", + " \"neither\",\n", + " \"mostly correct\",\n", + " \"completely correct\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Explanation Clarity\",\n", + " \"description\": \"Clarity and comprehensibility of explanations, including language use and structure\",\n", + " \"accepted_values\": [\n", + " \"not at all clear\",\n", + " \"slightly clear\",\n", + " \"moderately clear\",\n", + " \"very clear\",\n", + " \"completely clear\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Code Efficiency\",\n", + " \"description\": \"Quality of code in terms of efficiency and elegance\",\n", + " \"accepted_values\": [\n", + " \"not at all efficient\",\n", + " \"slightly efficient\",\n", + " \"moderately efficient\",\n", + " \"very efficient\",\n", + " \"extremely efficient\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Code Correctness\",\n", + " \"description\": \"Correctness of the provided code\",\n", + " \"accepted_values\": [\n", + " \"completely incorrect\",\n", + " \"mostly incorrect\",\n", + " \"partly correct\",\n", + " \"mostly correct\",\n", + " \"completely correct\"\n", + " ],\n", + " \"sub_criteria\": []\n", " }\n", - "actual test case to evaluate: {\n", - " \"problem\": \"Bekah has exactly three brass house number digits: 2, 3 and 5. How many distinct numbers can she form using one or more of the digits?\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Prealgebra\",\n", - " \"solution\": \"If Bekah uses only one digit, she can form three numbers. If she uses two digits, she has three choices for the tens place and two for the units, so she can form six numbers. Finally, if Bekah uses all three digits, she has three choices for the hundreds place, two for the tens, and one for the units, so she can form six numbers. Thus, Bekah can form $3 + 6 + 6 = \\\\boxed{15}$ distinct numbers.\",\n", - " \"problem_id\": \"11\",\n", - " \"response_with_ans\": \"To find the answer, we'll consider all possible combinations that Bekah can make using the three digits she has:\\n1. Single-digit numbers: 2, 3, and 5\\n2. Two-digits numbers using the digits: 23, 25, 32, 35, 52, and 53\\n3. Three-digits number using all the digits: 235, 253, 325, 352, 523, and 532\\n\\nLet's calculate the number of distinct numbers Bekah can form using one or more of the digits with Python code:\\n\\n```python\\nfrom itertools import permutations\\n\\ndigits = [2, 3, 5]\\n\\n# Single-digit numbers\\none_digit_numbers = set(digits)\\n\\n# Two-digits numbers\\ntwo_digit_numbers = set(int(''.join(map(str, p))) for p in permutations(digits, 2))\\n\\n# Three-digits numbers\\nthree_digit_numbers = set(int(''.join(map(str, p))) for p in permutations(digits, 3))\\n\\ndistinct_numbers = one_digit_numbers | two_digit_numbers | three_digit_numbers\\ntotal_distinct_numbers = len(distinct_numbers)\\n\\nprint(\\\"Distinct numbers:\\\", distinct_numbers)\\nprint(\\\"Total distinct numbers:\\\", total_distinct_numbers)\\n```\\n\\nPlease run the above Python code to calculate the total number of distinct numbers Bekah can form using one or more of the digits.\",\n", - " \"round\": 0,\n", - " \"messages\": [\n", - " {\n", - " \"content\": \"Bekah has exactly three brass house number digits: 2, 3 and 5. How many distinct numbers can she form using one or more of the digits?\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"To find the answer, we'll consider all possible combinations that Bekah can make using the three digits she has:\\n1. Single-digit numbers: 2, 3, and 5\\n2. Two-digits numbers using the digits: 23, 25, 32, 35, 52, and 53\\n3. Three-digits number using all the digits: 235, 253, 325, 352, 523, and 532\\n\\nLet's calculate the number of distinct numbers Bekah can form using one or more of the digits with Python code:\\n\\n```python\\nfrom itertools import permutations\\n\\ndigits = [2, 3, 5]\\n\\n# Single-digit numbers\\none_digit_numbers = set(digits)\\n\\n# Two-digits numbers\\ntwo_digit_numbers = set(int(''.join(map(str, p))) for p in permutations(digits, 2))\\n\\n# Three-digits numbers\\nthree_digit_numbers = set(int(''.join(map(str, p))) for p in permutations(digits, 3))\\n\\ndistinct_numbers = one_digit_numbers | two_digit_numbers | three_digit_numbers\\ntotal_distinct_numbers = len(distinct_numbers)\\n\\nprint(\\\"Distinct numbers:\\\", distinct_numbers)\\nprint(\\\"Total distinct numbers:\\\", total_distinct_numbers)\\n```\\n\\nPlease run the above Python code to calculate the total number of distinct numbers Bekah can form using one or more of the digits.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 15.620970249176025,\n", - " \"trial\": -1\n", - "}\n", + "]actual test case to evaluate: {'problem': 'Each interior angle of a polygon measures 170 degrees. How many sides does the polygon have?', 'level': 'Level 5', 'type': 'Prealgebra', 'solution': 'The sum of the interior angles of a polygon is $180(n-2)$, where $n$ is the number of sides. That means each interior angle has a measure of $\\\\frac{180(n-2)}{n}$ degrees. We set this equal to 170 degrees and solve for $n$. \\\\begin{align*}\\n\\\\frac{180(n-2)}{n}&=170\\\\\\\\\\n\\\\Rightarrow \\\\qquad 180n-360&=170n\\\\\\\\\\\\Rightarrow \\\\qquad 10n&=360\\\\\\\\\\n\\\\Rightarrow\\\\qquad n&=36.\\n\\\\end{align*} The polygon has $\\\\boxed{36}$ sides.', 'problem_id': '14', 'response_with_ans': \"Quick note: A single backtick should be used for inline code, and triple backticks should be used for code blocks. It seems I accidentally used single backticks for the code block in my previous response. Please use the code below instead:\\n\\n```python\\nfrom sympy import symbols, Eq, solve\\n\\nn = symbols('n')\\nequation = Eq(n * 170, (n - 2) * 180)\\nsides = solve(equation, n)[0]\\nprint(sides)\\n```\\n\", 'round': 1, 'messages': [{'content': 'Each interior angle of a polygon measures 170 degrees. How many sides does the polygon have?', 'role': 'user'}, {'content': \"We can use the formula for the sum of the interior angles of a polygon to solve this problem. The formula is:\\n\\nsum_of_interior_angles = (n - 2) * 180\\n\\nwhere n is the number of sides.\\n\\nAs each interior angle measures 170 degrees, we can write the equation:\\n\\nn * 170 = (n - 2) * 180\\n\\nLet's solve for the number of sides (n) using Python code.\\n\\n```python\\nfrom sympy import symbols, Eq, solve\\n\\nn = symbols('n')\\nequation = Eq(n * 170, (n - 2) * 180)\\nsides = solve(equation, n)[0]\\nsides\\n\", 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': \"Quick note: A single backtick should be used for inline code, and triple backticks should be used for code blocks. It seems I accidentally used single backticks for the code block in my previous response. Please use the code below instead:\\n\\n```python\\nfrom sympy import symbols, Eq, solve\\n\\nn = symbols('n')\\nequation = Eq(n * 170, (n - 2) * 180)\\nsides = solve(equation, n)[0]\\nprint(sides)\\n```\\n\", 'role': 'assistant'}], 'time': 16.42014193534851, 'trial': -1}\n", "\n", "--------------------------------------------------------------------------------\n", + "\u001b[31m\n", + ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", "\u001b[33mquantifier\u001b[0m (to quantifier_user):\n", "\n", "{\n", - " \"Problem Interpretation\": \"completely accurate\",\n", - " \"Mathematical Methodology\": \"completely effective\",\n", - " \"Calculation Correctness\": \"completely correct\",\n", - " \"Explanation Clarity\": \"completely clear\",\n", - " \"Code Efficiency\": \"very efficient\",\n", - " \"Code Correctness\": \"completely correct\"\n", + " \"Problem Interpretation\": \"completely accurate\",\n", + " \"Mathematical Methodology\": \"completely effective\",\n", + " \"Calculation Correctness\": \"completely correct\",\n", + " \"Explanation Clarity\": \"very clear\",\n", + " \"Code Efficiency\": \"moderately efficient\",\n", + " \"Code Correctness\": \"completely correct\"\n", "}\n", "\n", "--------------------------------------------------------------------------------\n", - "actual label for this case: false\n", "\u001b[33mquantifier_user\u001b[0m (to quantifier):\n", "\n", "Task: Math problem solving.\n", - "Task description: Given any question, the system needs to solve the problem as consisely and accurately as possible\n", - "Task successful example: {\n", - " \"problem\": \"What is the sum of all the distinct positive two-digit factors of 144?\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Number Theory\",\n", - " \"solution\": \"Prime factorize $144=2^4\\\\cdot3^2$. The sum of the positive two-digit factors of 144 is $2^4+2\\\\cdot3^2+2^2\\\\cdot3+2^2\\\\cdot3^2+2^3\\\\cdot3+2^3\\\\cdot3^2+2^4\\\\cdot3=\\\\boxed{226}.$\",\n", - " \"problem_id\": \"0\",\n", - " \"response_with_ans\": \"To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere's a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\\\"The sum of all the distinct positive two-digit factors of 144 is:\\\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.\",\n", - " \"round\": 0,\n", - " \"messages\": [\n", - " {\n", - " \"content\": \"What is the sum of all the distinct positive two-digit factors of 144?\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere's a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\\\"The sum of all the distinct positive two-digit factors of 144 is:\\\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 11.140539407730103,\n", - " \"trial\": -1\n", - "}\n", + " Task description: Given any question, the system needs to solve the problem as consisely and accurately as possible\n", + " Task successful example: {'problem': 'What is the sum of all the distinct positive two-digit factors of 144?', 'level': 'Level 5', 'type': 'Number Theory', 'solution': 'Prime factorize $144=2^4\\\\cdot3^2$. The sum of the positive two-digit factors of 144 is $2^4+2\\\\cdot3^2+2^2\\\\cdot3+2^2\\\\cdot3^2+2^3\\\\cdot3+2^3\\\\cdot3^2+2^4\\\\cdot3=\\\\boxed{226}.$', 'problem_id': '0', 'response_with_ans': 'To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere\\'s a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\"The sum of all the distinct positive two-digit factors of 144 is:\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.', 'round': 0, 'messages': [{'content': 'What is the sum of all the distinct positive two-digit factors of 144?', 'role': 'user'}, {'content': 'To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere\\'s a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\"The sum of all the distinct positive two-digit factors of 144 is:\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.', 'role': 'assistant'}], 'time': 11.140539407730103, 'trial': -1}\n", + " Task failed example: {'problem': 'Find all $x$ that satisfy the inequality $(2x+10)(x+3)<(3x+9)(x+8)$. Express your answer in interval notation.', 'level': 'Level 5', 'type': 'Algebra', 'solution': 'We have \\\\begin{align*} (2x+10)(x+3)&<(3x+9)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)&<3(x+3)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)-3(x+3)(x+8)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (2x+10-(3x+24))(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (-x-14)(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (x+14)(x+3)&>0.\\n\\\\end{align*} This inequality is satisfied if and only if $(x+14)$ and $(x+3)$ are either both positive or both negative. Both factors are positive for $x>-3$ and both factors are negative for $x<-14$. When $-14 0$ and then find intervals where the inequality holds true.\\n\\nLet's first find the factorized form of the quadratic:\", 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': \"I apologize for the confusion in my previous message. Let's find the factorized form of the quadratic using Python code.\\n\\n```python\\nfrom sympy import symbols, factor\\r\\nx = symbols('x')\\r\\nfactorized = factor(x**2 + 17*x + 42)\\r\\nfactorized\\r\\n```\\n\\nPlease run the Python code above to find the factorized form of the quadratic, and we can proceed to determine the intervals where the inequality holds true.\", 'role': 'assistant'}], 'time': 24.91333508491516, 'trial': -1}\n", + " Evaluation dictionary: [\n", + " {\n", + " \"name\": \"Problem Interpretation\",\n", + " \"description\": \"Ability to correctly interpret the problem.\",\n", + " \"accepted_values\": [\n", + " \"completely off\",\n", + " \"slightly relevant\",\n", + " \"relevant\",\n", + " \"mostly accurate\",\n", + " \"completely accurate\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Mathematical Methodology\",\n", + " \"description\": \"Adequacy of the chosen mathematical or algorithmic methodology for the question\",\n", + " \"accepted_values\": [\n", + " \"inappropriate\",\n", + " \"barely adequate\",\n", + " \"adequate\",\n", + " \"mostly effective\",\n", + " \"completely effective\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Calculation Correctness\",\n", + " \"description\": \"Accuracy of calculations made and solutions given\",\n", + " \"accepted_values\": [\n", + " \"completely incorrect\",\n", + " \"mostly incorrect\",\n", + " \"neither\",\n", + " \"mostly correct\",\n", + " \"completely correct\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Explanation Clarity\",\n", + " \"description\": \"Clarity and comprehensibility of explanations, including language use and structure\",\n", + " \"accepted_values\": [\n", + " \"not at all clear\",\n", + " \"slightly clear\",\n", + " \"moderately clear\",\n", + " \"very clear\",\n", + " \"completely clear\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Code Efficiency\",\n", + " \"description\": \"Quality of code in terms of efficiency and elegance\",\n", + " \"accepted_values\": [\n", + " \"not at all efficient\",\n", + " \"slightly efficient\",\n", + " \"moderately efficient\",\n", + " \"very efficient\",\n", + " \"extremely efficient\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Code Correctness\",\n", + " \"description\": \"Correctness of the provided code\",\n", + " \"accepted_values\": [\n", + " \"completely incorrect\",\n", + " \"mostly incorrect\",\n", + " \"partly correct\",\n", + " \"mostly correct\",\n", + " \"completely correct\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " }\n", + "]actual test case to evaluate: {'problem': 'An equilateral triangle has a side of length 12 inches. What is the area of the triangle, in square inches? Express your answer in simplest radical form.', 'level': 'Level 5', 'type': 'Prealgebra', 'solution': 'The area of an equilateral triangle with side length $s$ is $s^2\\\\sqrt{3}/4$. We have $s = 12$, so our area is $12^2\\\\sqrt{3}/4 = \\\\boxed{36\\\\sqrt{3}}$.', 'problem_id': '18', 'response_with_ans': '```python\\nimport math\\n\\nside_length = 12\\narea = (math.sqrt(3) * side_length**2) / 4\\nprint(area)\\n```', 'round': 1, 'messages': [{'content': 'An equilateral triangle has a side of length 12 inches. What is the area of the triangle, in square inches? Express your answer in simplest radical form.', 'role': 'user'}, {'content': \"To find the area of an equilateral triangle with a side of length s, we can use the formula:\\n\\narea = (sqrt(3) * sΒ²) / 4\\n\\nIn this case, s = 12 inches. Let's calculate the area.\", 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': '```python\\nimport math\\n\\nside_length = 12\\narea = (math.sqrt(3) * side_length**2) / 4\\nprint(area)\\n```', 'role': 'assistant'}], 'time': 14.153439283370972, 'trial': -1}\n", "\n", - "Task failed example: {\n", - " \"problem\": \"Find all $x$ that satisfy the inequality $(2x+10)(x+3)<(3x+9)(x+8)$. Express your answer in interval notation.\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Algebra\",\n", - " \"solution\": \"We have \\\\begin{align*} (2x+10)(x+3)&<(3x+9)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)&<3(x+3)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)-3(x+3)(x+8)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (2x+10-(3x+24))(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (-x-14)(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (x+14)(x+3)&>0.\\n\\\\end{align*} This inequality is satisfied if and only if $(x+14)$ and $(x+3)$ are either both positive or both negative. Both factors are positive for $x>-3$ and both factors are negative for $x<-14$. When $-14 0$ and then find intervals where the inequality holds true.\\n\\nLet's first find the factorized form of the quadratic:\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"I apologize for the confusion in my previous message. Let's find the factorized form of the quadratic using Python code.\\n\\n```python\\nfrom sympy import symbols, factor\\r\\nx = symbols('x')\\r\\nfactorized = factor(x**2 + 17*x + 42)\\r\\nfactorized\\r\\n```\\n\\nPlease run the Python code above to find the factorized form of the quadratic, and we can proceed to determine the intervals where the inequality holds true.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 24.91333508491516,\n", - " \"trial\": -1\n", + "--------------------------------------------------------------------------------\n", + "\u001b[31m\n", + ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", + "\u001b[33mquantifier\u001b[0m (to quantifier_user):\n", + "\n", + "{\n", + " \"Problem Interpretation\": \"completely accurate\",\n", + " \"Mathematical Methodology\": \"completely effective\",\n", + " \"Calculation Correctness\": \"completely correct\",\n", + " \"Explanation Clarity\": \"completely clear\",\n", + " \"Code Efficiency\": \"moderately efficient\",\n", + " \"Code Correctness\": \"completely correct\"\n", "}\n", "\n", - "Evaluation dictionary: {\n", - " \"Problem Interpretation\": {\n", - " \"description\": \"Ability to correctly interpret the problem.\",\n", - " \"accepted_values\": [\"completely off\", \"slightly relevant\", \"relevant\", \"mostly accurate\", \"completely accurate\"]\n", - " },\n", - " \"Mathematical Methodology\": {\n", - " \"description\": \"Adequacy of the chosen mathematical or algorithmic methodology for the question\",\n", - " \"accepted_values\": [\"inappropriate\", \"barely adequate\", \"adequate\", \"mostly effective\", \"completely effective\"]\n", - " },\n", - " \"Calculation Correctness\": {\n", - " \"description\": \"Accuracy of calculations made and solutions given\",\n", - " \"accepted_values\": [\"completely incorrect\", \"mostly incorrect\", \"neither\", \"mostly correct\", \"completely correct\"]\n", - " },\n", - " \"Explanation Clarity\": {\n", - " \"description\": \"Clarity and comprehensibility of explanations, including language use and structure\",\n", - " \"accepted_values\": [\"not at all clear\", \"slightly clear\", \"moderately clear\", \"very clear\", \"completely clear\"]\n", - " },\n", - " \"Code Efficiency\": {\n", - " \"description\": \"Quality of code in terms of efficiency and elegance\",\n", - " \"accepted_values\": [\"not at all efficient\", \"slightly efficient\", \"moderately efficient\", \"very efficient\", \"extremely efficient\"]\n", - " },\n", - " \"Code Correctness\": {\n", - " \"description\": \"Correctness of the provided code\",\n", - " \"accepted_values\": [\"completely incorrect\", \"mostly incorrect\", \"partly correct\", \"mostly correct\", \"completely correct\"]\n", - " }\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mquantifier_user\u001b[0m (to quantifier):\n", + "\n", + "Task: Math problem solving.\n", + " Task description: Given any question, the system needs to solve the problem as consisely and accurately as possible\n", + " Task successful example: {'problem': 'What is the sum of all the distinct positive two-digit factors of 144?', 'level': 'Level 5', 'type': 'Number Theory', 'solution': 'Prime factorize $144=2^4\\\\cdot3^2$. The sum of the positive two-digit factors of 144 is $2^4+2\\\\cdot3^2+2^2\\\\cdot3+2^2\\\\cdot3^2+2^3\\\\cdot3+2^3\\\\cdot3^2+2^4\\\\cdot3=\\\\boxed{226}.$', 'problem_id': '0', 'response_with_ans': 'To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere\\'s a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\"The sum of all the distinct positive two-digit factors of 144 is:\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.', 'round': 0, 'messages': [{'content': 'What is the sum of all the distinct positive two-digit factors of 144?', 'role': 'user'}, {'content': 'To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere\\'s a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\"The sum of all the distinct positive two-digit factors of 144 is:\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.', 'role': 'assistant'}], 'time': 11.140539407730103, 'trial': -1}\n", + " Task failed example: {'problem': 'Find all $x$ that satisfy the inequality $(2x+10)(x+3)<(3x+9)(x+8)$. Express your answer in interval notation.', 'level': 'Level 5', 'type': 'Algebra', 'solution': 'We have \\\\begin{align*} (2x+10)(x+3)&<(3x+9)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)&<3(x+3)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)-3(x+3)(x+8)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (2x+10-(3x+24))(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (-x-14)(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (x+14)(x+3)&>0.\\n\\\\end{align*} This inequality is satisfied if and only if $(x+14)$ and $(x+3)$ are either both positive or both negative. Both factors are positive for $x>-3$ and both factors are negative for $x<-14$. When $-14 0$ and then find intervals where the inequality holds true.\\n\\nLet's first find the factorized form of the quadratic:\", 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': \"I apologize for the confusion in my previous message. Let's find the factorized form of the quadratic using Python code.\\n\\n```python\\nfrom sympy import symbols, factor\\r\\nx = symbols('x')\\r\\nfactorized = factor(x**2 + 17*x + 42)\\r\\nfactorized\\r\\n```\\n\\nPlease run the Python code above to find the factorized form of the quadratic, and we can proceed to determine the intervals where the inequality holds true.\", 'role': 'assistant'}], 'time': 24.91333508491516, 'trial': -1}\n", + " Evaluation dictionary: [\n", + " {\n", + " \"name\": \"Problem Interpretation\",\n", + " \"description\": \"Ability to correctly interpret the problem.\",\n", + " \"accepted_values\": [\n", + " \"completely off\",\n", + " \"slightly relevant\",\n", + " \"relevant\",\n", + " \"mostly accurate\",\n", + " \"completely accurate\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Mathematical Methodology\",\n", + " \"description\": \"Adequacy of the chosen mathematical or algorithmic methodology for the question\",\n", + " \"accepted_values\": [\n", + " \"inappropriate\",\n", + " \"barely adequate\",\n", + " \"adequate\",\n", + " \"mostly effective\",\n", + " \"completely effective\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Calculation Correctness\",\n", + " \"description\": \"Accuracy of calculations made and solutions given\",\n", + " \"accepted_values\": [\n", + " \"completely incorrect\",\n", + " \"mostly incorrect\",\n", + " \"neither\",\n", + " \"mostly correct\",\n", + " \"completely correct\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Explanation Clarity\",\n", + " \"description\": \"Clarity and comprehensibility of explanations, including language use and structure\",\n", + " \"accepted_values\": [\n", + " \"not at all clear\",\n", + " \"slightly clear\",\n", + " \"moderately clear\",\n", + " \"very clear\",\n", + " \"completely clear\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Code Efficiency\",\n", + " \"description\": \"Quality of code in terms of efficiency and elegance\",\n", + " \"accepted_values\": [\n", + " \"not at all efficient\",\n", + " \"slightly efficient\",\n", + " \"moderately efficient\",\n", + " \"very efficient\",\n", + " \"extremely efficient\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Code Correctness\",\n", + " \"description\": \"Correctness of the provided code\",\n", + " \"accepted_values\": [\n", + " \"completely incorrect\",\n", + " \"mostly incorrect\",\n", + " \"partly correct\",\n", + " \"mostly correct\",\n", + " \"completely correct\"\n", + " ],\n", + " \"sub_criteria\": []\n", " }\n", - "actual test case to evaluate: {\n", - " \"problem\": \"In the diagram, $AB,$ $BC,$ $CD,$ $DE,$ $EF,$ $FG,$ $GH,$ and $HK$ all have length $4,$ and all angles are right angles, with the exception of the angles at $D$ and $F.$\\n\\n[asy]\\ndraw((0,0)--(0,4)--(4,4)--(4,8)--(6.8284,5.1716)--(9.6569,8)--(9.6569,4)--(13.6569,4)--(13.6569,0)--cycle,black+linewidth(1));\\ndraw((0,0)--(0.5,0)--(0.5,0.5)--(0,0.5)--cycle,black+linewidth(1));\\ndraw((0,4)--(0.5,4)--(0.5,3.5)--(0,3.5)--cycle,black+linewidth(1));\\ndraw((4,4)--(4,4.5)--(3.5,4.5)--(3.5,4)--cycle,black+linewidth(1));\\ndraw((6.8284,5.1716)--(7.0784,5.4216)--(6.8284,5.6716)--(6.5784,5.4216)--cycle,black+linewidth(1));\\ndraw((9.6569,4)--(10.1569,4)--(10.1569,4.5)--(9.6569,4.5)--cycle,black+linewidth(1));\\ndraw((13.6569,4)--(13.1569,4)--(13.1569,3.5)--(13.6569,3.5)--cycle,black+linewidth(1));\\ndraw((13.6569,0)--(13.1569,0)--(13.1569,0.5)--(13.6569,0.5)--cycle,black+linewidth(1));\\nlabel(\\\"$A$\\\",(0,0),W);\\nlabel(\\\"$B$\\\",(0,4),NW);\\nlabel(\\\"$C$\\\",(4,4),S);\\nlabel(\\\"$D$\\\",(4,8),N);\\nlabel(\\\"$E$\\\",(6.8284,5.1716),S);\\nlabel(\\\"$F$\\\",(9.6569,8),N);\\nlabel(\\\"$G$\\\",(9.6569,4),S);\\nlabel(\\\"$H$\\\",(13.6569,4),NE);\\nlabel(\\\"$K$\\\",(13.6569,0),E);\\n[/asy]\\n\\nDetermine the length of $DF.$\\n\\n[asy]\\ndraw((0,0)--(2.8284,-2.8284)--(5.6568,0),black+linewidth(1));\\ndraw((0,0)--(5.6568,0),black+linewidth(1)+dashed);\\ndraw((2.8284,-2.8284)--(3.0784,-2.5784)--(2.8284,-2.3284)--(2.5784,-2.5784)--cycle,black+linewidth(1));\\nlabel(\\\"$D$\\\",(0,0),N);\\nlabel(\\\"$E$\\\",(2.8284,-2.8284),S);\\nlabel(\\\"$F$\\\",(5.6568,0),N);\\n[/asy]\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Prealgebra\",\n", - " \"solution\": \"Since $DE=EF=4$ and $\\\\angle DEF = 90^\\\\circ,$ by the Pythagorean Theorem, \\\\begin{align*}\\nDF^2 &= DE^2+EF^2 \\\\\\\\\\n&= 4^2+4^2 \\\\\\\\\\n&=32,\\n\\\\end{align*}so that $DF = \\\\sqrt{32}=\\\\boxed{4\\\\sqrt{2}}.$\",\n", - " \"problem_id\": \"16\",\n", - " \"response_with_ans\": \"Now let's calculate the square of DF using Python.\\n\\n```python\\nDH = 9.6569\\nHG = 5.6569\\ncos_alpha_beta = 0\\n\\nDF_squared = DH**2 + HG**2 - 2 * DH * HG * cos_alpha_beta\\nDF_squared\\n```\",\n", - " \"round\": 2,\n", - " \"messages\": [\n", - " {\n", - " \"content\": \"In the diagram, $AB,$ $BC,$ $CD,$ $DE,$ $EF,$ $FG,$ $GH,$ and $HK$ all have length $4,$ and all angles are right angles, with the exception of the angles at $D$ and $F.$\\n\\n[asy]\\ndraw((0,0)--(0,4)--(4,4)--(4,8)--(6.8284,5.1716)--(9.6569,8)--(9.6569,4)--(13.6569,4)--(13.6569,0)--cycle,black+linewidth(1));\\ndraw((0,0)--(0.5,0)--(0.5,0.5)--(0,0.5)--cycle,black+linewidth(1));\\ndraw((0,4)--(0.5,4)--(0.5,3.5)--(0,3.5)--cycle,black+linewidth(1));\\ndraw((4,4)--(4,4.5)--(3.5,4.5)--(3.5,4)--cycle,black+linewidth(1));\\ndraw((6.8284,5.1716)--(7.0784,5.4216)--(6.8284,5.6716)--(6.5784,5.4216)--cycle,black+linewidth(1));\\ndraw((9.6569,4)--(10.1569,4)--(10.1569,4.5)--(9.6569,4.5)--cycle,black+linewidth(1));\\ndraw((13.6569,4)--(13.1569,4)--(13.1569,3.5)--(13.6569,3.5)--cycle,black+linewidth(1));\\ndraw((13.6569,0)--(13.1569,0)--(13.1569,0.5)--(13.6569,0.5)--cycle,black+linewidth(1));\\nlabel(\\\"$A$\\\",(0,0),W);\\nlabel(\\\"$B$\\\",(0,4),NW);\\nlabel(\\\"$C$\\\",(4,4),S);\\nlabel(\\\"$D$\\\",(4,8),N);\\nlabel(\\\"$E$\\\",(6.8284,5.1716),S);\\nlabel(\\\"$F$\\\",(9.6569,8),N);\\nlabel(\\\"$G$\\\",(9.6569,4),S);\\nlabel(\\\"$H$\\\",(13.6569,4),NE);\\nlabel(\\\"$K$\\\",(13.6569,0),E);\\n[/asy]\\n\\nDetermine the length of $DF.$\\n\\n[asy]\\ndraw((0,0)--(2.8284,-2.8284)--(5.6568,0),black+linewidth(1));\\ndraw((0,0)--(5.6568,0),black+linewidth(1)+dashed);\\ndraw((2.8284,-2.8284)--(3.0784,-2.5784)--(2.8284,-2.3284)--(2.5784,-2.5784)--cycle,black+linewidth(1));\\nlabel(\\\"$D$\\\",(0,0),N);\\nlabel(\\\"$E$\\\",(2.8284,-2.8284),S);\\nlabel(\\\"$F$\\\",(5.6568,0),N);\\n[/asy]\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"Let's use coordinate geometry to determine the coordinates of points D, E, and F, which will allow us to compute the Euclidean distance between points D and F.\\n\\n1. Assign coordinates to points A, B, C, D, E, F, G, H, and K.\\n2. Calculate the slope and equation of line DE and line FG.\\n3. Use the slopes and the coordinates of points E and G to compute the coordinates of point F.\\n4. Calculate the length of DF using the distance formula.\\n\\nLet's start by assigning coordinates:\\n\\n1. A(0, 0), B(0, 4), C(4, 4), D(4, 8), G(9.6569, 4), H(13.6569, 4), and K(13.6569, 0).\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"Since the angles at points D and F are not right angles, we need to use some trigonometry to find points E and F. We already have the coordinates of points D and G.\\n\\nLet \\u03b1 be the angle between the horizontal line and the line DE, and \\u03b2 be the angle between the horizontal line and the line FG.\\n\\nLet P be the intersection point between the horizontal lines passing through points D and F. Therefore, the triangle DPF is a right triangle. \\n\\nSince we know the angles \\u03b1, \\u03b2, and the length DG, we can determine the length DF using the law of cosines:\\n\\nLet's calculate the length of DG first:\\n\\nDG = DH + HG = (13.6569 - 4) + (9.6569 - 4) = 9.6569 + 5.6569 = 15.3138\\n\\nNow, let \\u03b1 be the angle between line DH and DF, and \\u03b2 be the angle between line HG and DF. We can use the law of cosines to calculate the length of DF:\\n\\nDF^2 = DH^2 + HG^2 - 2 * DH * HG * cos(\\u03b1 + \\u03b2)\\n\\nWe also know angle DAB = \\u03b1, angle GHB = \\u03b2 and AB = FG = 4.\\n\\nWe can calculate cos(\\u03b1 + \\u03b2) using the cosine formula for the addition of angles:\\n\\ncos(\\u03b1 + \\u03b2) = cos(\\u03b1) * cos(\\u03b2) - sin(\\u03b1) * sin(\\u03b2)\\n\\nTo find sin(\\u03b1) and cos(\\u03b1), we can use right triangle with sides DA, AB, and DB. Since DB = BA = 4, we have:\\n\\ncos(\\u03b1) = AD / DB = 4 / 4 = 1\\nsin(\\u03b1) = BA / DB = 4 / 4 = 1\\n\\nSimilarly, for angle \\u03b2, we have right triangle with sides GH, HB, and GB. Since GB = BH = 4, we have:\\n\\ncos(\\u03b2) = HG / GB = 4 / 4 = 1\\nsin(\\u03b2) = HB / GB = 4 / 4 = 1\\n\\nNow, let's calculate cos(\\u03b1 + \\u03b2):\\n\\ncos(\\u03b1 + \\u03b2) = cos(\\u03b1) * cos(\\u03b2) - sin(\\u03b1) * sin(\\u03b2) = 1 * 1 - 1 * 1 = 0\\n\\nFinally, let's calculate DF using the law of cosines:\\n\\nDF^2 = DH^2 + HG^2 - 2 * DH * HG * cos(\\u03b1 + \\u03b2)\\nDF^2 = 9.6569^2 + 5.6569^2 - 2 * 9.6569 * 5.6569 * 0\\n\\nNow let's compute the square of DF.\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"Now let's calculate the square of DF using Python.\\n\\n```python\\nDH = 9.6569\\nHG = 5.6569\\ncos_alpha_beta = 0\\n\\nDF_squared = DH**2 + HG**2 - 2 * DH * HG * cos_alpha_beta\\nDF_squared\\n```\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 44.82826566696167,\n", - " \"trial\": -1\n", - "}\n", + "]actual test case to evaluate: {'problem': 'Simplify: $\\\\frac{\\\\sqrt{2.5^2-0.7^2}}{2.7-2.5}$.', 'level': 'Level 5', 'type': 'Prealgebra', 'solution': 'We have \\\\begin{align*}\\n\\\\frac{\\\\sqrt{2.5^2 - 0.7^2}}{2.7-2.5} &= \\\\frac{\\\\sqrt{6.25 - 0.49}}{2.7-2.5} = \\\\frac{\\\\sqrt{5.76}}{0.2} = \\\\frac{\\\\sqrt{576/100}}{0.2}\\\\\\\\\\n&= \\\\frac{\\\\sqrt{576}/\\\\sqrt{100}}{0.2} = \\\\frac{24/10}{0.2} = \\\\frac{2.4}{0.2} = \\\\boxed{12}.\\\\end{align*}', 'problem_id': '2', 'response_with_ans': '```python\\nimport math\\n\\nnumerator = math.sqrt(2.5 ** 2 - 0.7 ** 2)\\ndenominator = 2.7 - 2.5\\n\\nresult = numerator / denominator\\nresult\\n```\\n', 'round': 1, 'messages': [{'content': 'Simplify: $\\\\frac{\\\\sqrt{2.5^2-0.7^2}}{2.7-2.5}$.', 'role': 'user'}, {'content': \"To simplify the given expression, let's break it down step by step and use Python to calculate the values:\\n\\nStep 1: Calculate the values inside the square root: $2.5^2 - 0.7^2$\\nStep 2: Take the square root of the result\\nStep 3: Calculate the denominator: $2.7 - 2.5$\\nStep 4: Divide the result of Step 2 by the result of Step 3\\n\\nNow, let's execute Python code to get the result.\", 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': '```python\\nimport math\\n\\nnumerator = math.sqrt(2.5 ** 2 - 0.7 ** 2)\\ndenominator = 2.7 - 2.5\\n\\nresult = numerator / denominator\\nresult\\n```\\n', 'role': 'assistant'}], 'time': 8.612567901611328, 'trial': -1}\n", "\n", "--------------------------------------------------------------------------------\n", + "\u001b[31m\n", + ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", "\u001b[33mquantifier\u001b[0m (to quantifier_user):\n", "\n", "{\n", @@ -3297,150 +2487,130 @@ " \"Mathematical Methodology\": \"completely effective\",\n", " \"Calculation Correctness\": \"completely correct\",\n", " \"Explanation Clarity\": \"very clear\",\n", - " \"Code Efficiency\": \"mostly efficient\",\n", - " \"Code Correctness\": \"mostly correct\"\n", + " \"Code Efficiency\": \"moderately efficient\",\n", + " \"Code Correctness\": \"completely correct\"\n", "}\n", "\n", "--------------------------------------------------------------------------------\n", - "actual label for this case: true\n", "\u001b[33mquantifier_user\u001b[0m (to quantifier):\n", "\n", "Task: Math problem solving.\n", - "Task description: Given any question, the system needs to solve the problem as consisely and accurately as possible\n", - "Task successful example: {\n", - " \"problem\": \"What is the sum of all the distinct positive two-digit factors of 144?\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Number Theory\",\n", - " \"solution\": \"Prime factorize $144=2^4\\\\cdot3^2$. The sum of the positive two-digit factors of 144 is $2^4+2\\\\cdot3^2+2^2\\\\cdot3+2^2\\\\cdot3^2+2^3\\\\cdot3+2^3\\\\cdot3^2+2^4\\\\cdot3=\\\\boxed{226}.$\",\n", - " \"problem_id\": \"0\",\n", - " \"response_with_ans\": \"To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere's a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\\\"The sum of all the distinct positive two-digit factors of 144 is:\\\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.\",\n", - " \"round\": 0,\n", - " \"messages\": [\n", - " {\n", - " \"content\": \"What is the sum of all the distinct positive two-digit factors of 144?\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere's a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\\\"The sum of all the distinct positive two-digit factors of 144 is:\\\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 11.140539407730103,\n", - " \"trial\": -1\n", - "}\n", - "\n", - "Task failed example: {\n", - " \"problem\": \"Find all $x$ that satisfy the inequality $(2x+10)(x+3)<(3x+9)(x+8)$. Express your answer in interval notation.\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Algebra\",\n", - " \"solution\": \"We have \\\\begin{align*} (2x+10)(x+3)&<(3x+9)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)&<3(x+3)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)-3(x+3)(x+8)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (2x+10-(3x+24))(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (-x-14)(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (x+14)(x+3)&>0.\\n\\\\end{align*} This inequality is satisfied if and only if $(x+14)$ and $(x+3)$ are either both positive or both negative. Both factors are positive for $x>-3$ and both factors are negative for $x<-14$. When $-14 0$ and then find intervals where the inequality holds true.\\n\\nLet's first find the factorized form of the quadratic:\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"I apologize for the confusion in my previous message. Let's find the factorized form of the quadratic using Python code.\\n\\n```python\\nfrom sympy import symbols, factor\\r\\nx = symbols('x')\\r\\nfactorized = factor(x**2 + 17*x + 42)\\r\\nfactorized\\r\\n```\\n\\nPlease run the Python code above to find the factorized form of the quadratic, and we can proceed to determine the intervals where the inequality holds true.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 24.91333508491516,\n", - " \"trial\": -1\n", - "}\n", - "\n", - "Evaluation dictionary: {\n", - " \"Problem Interpretation\": {\n", - " \"description\": \"Ability to correctly interpret the problem.\",\n", - " \"accepted_values\": [\"completely off\", \"slightly relevant\", \"relevant\", \"mostly accurate\", \"completely accurate\"]\n", - " },\n", - " \"Mathematical Methodology\": {\n", - " \"description\": \"Adequacy of the chosen mathematical or algorithmic methodology for the question\",\n", - " \"accepted_values\": [\"inappropriate\", \"barely adequate\", \"adequate\", \"mostly effective\", \"completely effective\"]\n", - " },\n", - " \"Calculation Correctness\": {\n", - " \"description\": \"Accuracy of calculations made and solutions given\",\n", - " \"accepted_values\": [\"completely incorrect\", \"mostly incorrect\", \"neither\", \"mostly correct\", \"completely correct\"]\n", - " },\n", - " \"Explanation Clarity\": {\n", - " \"description\": \"Clarity and comprehensibility of explanations, including language use and structure\",\n", - " \"accepted_values\": [\"not at all clear\", \"slightly clear\", \"moderately clear\", \"very clear\", \"completely clear\"]\n", - " },\n", - " \"Code Efficiency\": {\n", - " \"description\": \"Quality of code in terms of efficiency and elegance\",\n", - " \"accepted_values\": [\"not at all efficient\", \"slightly efficient\", \"moderately efficient\", \"very efficient\", \"extremely efficient\"]\n", - " },\n", - " \"Code Correctness\": {\n", - " \"description\": \"Correctness of the provided code\",\n", - " \"accepted_values\": [\"completely incorrect\", \"mostly incorrect\", \"partly correct\", \"mostly correct\", \"completely correct\"]\n", - " }\n", + " Task description: Given any question, the system needs to solve the problem as consisely and accurately as possible\n", + " Task successful example: {'problem': 'What is the sum of all the distinct positive two-digit factors of 144?', 'level': 'Level 5', 'type': 'Number Theory', 'solution': 'Prime factorize $144=2^4\\\\cdot3^2$. The sum of the positive two-digit factors of 144 is $2^4+2\\\\cdot3^2+2^2\\\\cdot3+2^2\\\\cdot3^2+2^3\\\\cdot3+2^3\\\\cdot3^2+2^4\\\\cdot3=\\\\boxed{226}.$', 'problem_id': '0', 'response_with_ans': 'To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere\\'s a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\"The sum of all the distinct positive two-digit factors of 144 is:\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.', 'round': 0, 'messages': [{'content': 'What is the sum of all the distinct positive two-digit factors of 144?', 'role': 'user'}, {'content': 'To find the sum of all the distinct positive two-digit factors of 144, we need to first find all these factors. We can do this by iterating through the numbers from 10 to 99 and checking if they are factors of 144. Then, we can sum these factors and print their sum.\\n\\nHere\\'s a Python script to accomplish this:\\n\\n```python\\ntwo_digit_factors = []\\n\\nfor i in range(10, 100):\\n if 144 % i == 0:\\n two_digit_factors.append(i)\\n\\nsum_of_factors = sum(two_digit_factors)\\nprint(\"The sum of all the distinct positive two-digit factors of 144 is:\", sum_of_factors)\\n```\\n\\nPlease run this script to find the desired sum.', 'role': 'assistant'}], 'time': 11.140539407730103, 'trial': -1}\n", + " Task failed example: {'problem': 'Find all $x$ that satisfy the inequality $(2x+10)(x+3)<(3x+9)(x+8)$. Express your answer in interval notation.', 'level': 'Level 5', 'type': 'Algebra', 'solution': 'We have \\\\begin{align*} (2x+10)(x+3)&<(3x+9)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)&<3(x+3)(x+8) \\\\quad \\\\Rightarrow\\n\\\\\\\\ 2(x+5)(x+3)-3(x+3)(x+8)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (2x+10-(3x+24))(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (-x-14)(x+3)&<0 \\\\quad \\\\Rightarrow\\n\\\\\\\\ (x+14)(x+3)&>0.\\n\\\\end{align*} This inequality is satisfied if and only if $(x+14)$ and $(x+3)$ are either both positive or both negative. Both factors are positive for $x>-3$ and both factors are negative for $x<-14$. When $-14 0$ and then find intervals where the inequality holds true.\\n\\nLet's first find the factorized form of the quadratic:\", 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': \"I apologize for the confusion in my previous message. Let's find the factorized form of the quadratic using Python code.\\n\\n```python\\nfrom sympy import symbols, factor\\r\\nx = symbols('x')\\r\\nfactorized = factor(x**2 + 17*x + 42)\\r\\nfactorized\\r\\n```\\n\\nPlease run the Python code above to find the factorized form of the quadratic, and we can proceed to determine the intervals where the inequality holds true.\", 'role': 'assistant'}], 'time': 24.91333508491516, 'trial': -1}\n", + " Evaluation dictionary: [\n", + " {\n", + " \"name\": \"Problem Interpretation\",\n", + " \"description\": \"Ability to correctly interpret the problem.\",\n", + " \"accepted_values\": [\n", + " \"completely off\",\n", + " \"slightly relevant\",\n", + " \"relevant\",\n", + " \"mostly accurate\",\n", + " \"completely accurate\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Mathematical Methodology\",\n", + " \"description\": \"Adequacy of the chosen mathematical or algorithmic methodology for the question\",\n", + " \"accepted_values\": [\n", + " \"inappropriate\",\n", + " \"barely adequate\",\n", + " \"adequate\",\n", + " \"mostly effective\",\n", + " \"completely effective\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Calculation Correctness\",\n", + " \"description\": \"Accuracy of calculations made and solutions given\",\n", + " \"accepted_values\": [\n", + " \"completely incorrect\",\n", + " \"mostly incorrect\",\n", + " \"neither\",\n", + " \"mostly correct\",\n", + " \"completely correct\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Explanation Clarity\",\n", + " \"description\": \"Clarity and comprehensibility of explanations, including language use and structure\",\n", + " \"accepted_values\": [\n", + " \"not at all clear\",\n", + " \"slightly clear\",\n", + " \"moderately clear\",\n", + " \"very clear\",\n", + " \"completely clear\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Code Efficiency\",\n", + " \"description\": \"Quality of code in terms of efficiency and elegance\",\n", + " \"accepted_values\": [\n", + " \"not at all efficient\",\n", + " \"slightly efficient\",\n", + " \"moderately efficient\",\n", + " \"very efficient\",\n", + " \"extremely efficient\"\n", + " ],\n", + " \"sub_criteria\": []\n", + " },\n", + " {\n", + " \"name\": \"Code Correctness\",\n", + " \"description\": \"Correctness of the provided code\",\n", + " \"accepted_values\": [\n", + " \"completely incorrect\",\n", + " \"mostly incorrect\",\n", + " \"partly correct\",\n", + " \"mostly correct\",\n", + " \"completely correct\"\n", + " ],\n", + " \"sub_criteria\": []\n", " }\n", - "actual test case to evaluate: {\n", - " \"problem\": \"How many integers $n$ satisfy both of the inequalities $4n + 3 < 25$ and $-7n + 5 < 24$?\",\n", - " \"level\": \"Level 5\",\n", - " \"type\": \"Prealgebra\",\n", - " \"solution\": \"Subtract 3 and divide by 4 on both sides of the first inequality to obtain \\\\begin{align*}\\n4n + 3 &< 25 \\\\\\\\\\n\\\\Rightarrow\\\\qquad 4n &< 22 \\\\\\\\\\n\\\\Rightarrow\\\\qquad n &< 5.5.\\n\\\\end{align*}Similarly, the second inequality yields \\\\begin{align*}\\n-7n + 5 &< 24 \\\\\\\\\\n\\\\Rightarrow\\\\qquad -7n &< 19 \\\\\\\\\\n\\\\Rightarrow\\\\qquad n &> -\\\\frac{19}{7}.\\n\\\\end{align*}Therefore, we are looking for all the integers between $-\\\\frac{19}{7}$ and $5.5$. Since $-\\\\frac{19}{7}$ is between $-3$ and $-2$ and the largest integer less than $5.5$ is 5, we need to count the number of integers between $-2$ and $5$, inclusive. There are $5$ positive integers, $2$ negative integers, and zero, so there are $\\\\boxed{8}$ integers that satisfy both $4n + 3 < 25$ and $-7n + 5 < 24$.\",\n", - " \"problem_id\": \"10\",\n", - " \"response_with_ans\": \"Here's the code to solve the inequalities and find the intersection of their solutions:\\n\\n```python\\nfrom sympy import symbols, Eq, solve\\n\\nn = symbols(\\\"n\\\")\\ninequality_1 = 4 * n + 3 < 25\\ninequality_2 = -7 * n + 5 < 24\\n\\nsolution_1 = solve(inequality_1, n)\\nsolution_2 = solve(inequality_2, n)\\n\\nintersection = (max(solution_1[0], solution_2[0]), min(solution_1[1], solution_2[1]))\\n\\nprint(f\\\"Solution to inequality 1: {solution_1}\\\")\\nprint(f\\\"Solution to inequality 2: {solution_2}\\\")\\nprint(f\\\"Intersection of solutions: {intersection}\\\")\\n```\\n\\nExecute this code, and let's see the solutions for both inequalities and their intersection.\",\n", - " \"round\": 1,\n", - " \"messages\": [\n", - " {\n", - " \"content\": \"How many integers $n$ satisfy both of the inequalities $4n + 3 < 25$ and $-7n + 5 < 24$?\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"To find the number of integers $n$ that satisfy both inequalities, we'll first solve each inequality individually, and then find the intersection of the solutions.\\n\\nStep 1: Solve the inequalities\\n1. $4n + 3 < 25$\\n2. $-7n + 5 < 24$\\n\\nStep 2: Find the intersection of the solutions\\n\\nStep 3: Count the number of integers in the intersection\\n\\nFirst, let's solve the inequalities using the python code.\",\n", - " \"role\": \"assistant\"\n", - " },\n", - " {\n", - " \"content\": \"\",\n", - " \"role\": \"user\"\n", - " },\n", - " {\n", - " \"content\": \"Here's the code to solve the inequalities and find the intersection of their solutions:\\n\\n```python\\nfrom sympy import symbols, Eq, solve\\n\\nn = symbols(\\\"n\\\")\\ninequality_1 = 4 * n + 3 < 25\\ninequality_2 = -7 * n + 5 < 24\\n\\nsolution_1 = solve(inequality_1, n)\\nsolution_2 = solve(inequality_2, n)\\n\\nintersection = (max(solution_1[0], solution_2[0]), min(solution_1[1], solution_2[1]))\\n\\nprint(f\\\"Solution to inequality 1: {solution_1}\\\")\\nprint(f\\\"Solution to inequality 2: {solution_2}\\\")\\nprint(f\\\"Intersection of solutions: {intersection}\\\")\\n```\\n\\nExecute this code, and let's see the solutions for both inequalities and their intersection.\",\n", - " \"role\": \"assistant\"\n", - " }\n", - " ],\n", - " \"time\": 19.949471950531006,\n", - " \"trial\": -1\n", - "}\n", + "]actual test case to evaluate: {'problem': 'Simplify $\\\\frac{2m+8}{3}-\\\\frac{2-m}{3}$.', 'level': 'Level 5', 'type': 'Prealgebra', 'solution': 'Both fractions have the same denominator, so we can subtract them: \\\\[\\\\frac{2m+8}{3}-\\\\frac{2-m}{3}=\\\\frac{(2m+8)-(2-m)}{3}\\\\] Distributing the negative sign across the parentheses, we get \\\\[\\\\frac{2m+8-2-(-m)}{3}=\\\\frac{2m+8-2+m}{3}=\\\\frac{3m+6}{3}\\\\] Notice that every number in the numerator has a common factor of 3. We can use the distributive law in reverse to get \\\\[\\\\frac{3m+6}{3}=\\\\frac{3(m+2)}{3}=\\\\frac{\\\\cancel{3}(m+2)}{\\\\cancel{3}}=\\\\boxed{m+2}.\\\\]', 'problem_id': '19', 'response_with_ans': '', 'round': 15, 'messages': [{'content': 'Simplify $\\\\frac{2m+8}{3}-\\\\frac{2-m}{3}$.', 'role': 'user'}, {'content': 'To simplify the expression, we can combine the fractions as they have the same denominator:\\n\\n$\\\\frac{2m + 8}{3} - \\\\frac{2 - m}{3} = \\\\frac{(2m + 8) - (2 - m)}{3}$\\n\\nNow, I will calculate the numerator and finish simplifying the expression.', 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': '', 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': '', 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': '', 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': '', 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': '', 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': '', 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': '\\n', 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': '', 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': '', 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': '', 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': \"My apologies for the confusion in the previous responses. Let me simplify the expression step by step now. \\n\\nWe have the expression:\\n\\n$\\\\frac{2m + 8}{3} - \\\\frac{2 - m}{3}$\\n\\nWe can combine the fractions as they have the same denominator:\\n\\n$\\\\frac{(2m + 8) - (2 - m)}{3}$\\n\\nLet's further simplify the expression using Python code.\", 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': '', 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': '', 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': '', 'role': 'assistant'}, {'content': '', 'role': 'user'}, {'content': '', 'role': 'assistant'}], 'time': 37.317880630493164, 'trial': -1}\n", "\n", "--------------------------------------------------------------------------------\n", + "\u001b[31m\n", + ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", "\u001b[33mquantifier\u001b[0m (to quantifier_user):\n", "\n", + "```json\n", "{\n", " \"Problem Interpretation\": \"completely accurate\",\n", " \"Mathematical Methodology\": \"completely effective\",\n", " \"Calculation Correctness\": \"completely correct\",\n", " \"Explanation Clarity\": \"very clear\",\n", - " \"Code Efficiency\": \"moderately efficient\",\n", - " \"Code Correctness\": \"mostly correct\"\n", + " \"Code Efficiency\": \"not applicable\",\n", + " \"Code Correctness\": \"not applicable\"\n", "}\n", + "```\n", "\n", "--------------------------------------------------------------------------------\n" ] } ], "source": [ - "# log_path = \"../test/test_files/agenteval-in-out/agentchat_results/\"\n", "criteria_file = \"../test/test_files/agenteval-in-out/samples/sample_math_criteria.json\"\n", + "criteria = Criterion.parse_json_str(open(criteria_file, \"r\").read())\n", "outcome = {}\n", "\n", "for prefix in os.listdir(log_path):\n", " for file_name in os.listdir(log_path + \"/\" + prefix):\n", " gameid = prefix + \"_\" + file_name\n", " if file_name.split(\".\")[-1] == \"json\":\n", - " outcome[gameid] = get_quantifier(log_path + \"/\" + prefix + \"/\" + file_name, criteria_file)\n", + " test_case, ground_truth = remove_ground_truth(open(log_path + \"/\" + prefix + \"/\" + file_name, \"r\").read())\n", + " quantifier_output = quantify_criteria(\n", + " llm_config={\"config_list\": config_list},\n", + " criteria=criteria,\n", + " task=task,\n", + " test_case=test_case,\n", + " ground_truth=ground_truth,\n", + " )\n", + " outcome[gameid] = quantifier_output\n", "\n", "# store the evaluated problems\n", "with open(\"../test/test_files/agenteval-in-out/evaluated_problems.json\", \"w\") as file:\n", @@ -3465,7 +2635,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 18, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -3485,26 +2655,35 @@ "name": "stderr", "output_type": "stream", "text": [ - "/home/vscode/.local/lib/python3.10/site-packages/scipy/stats/_distn_infrastructure.py:2241: RuntimeWarning: invalid value encountered in multiply\n", + "/home/vscode/.local/lib/python3.10/site-packages/numpy/core/fromnumeric.py:3504: RuntimeWarning: Mean of empty slice.\n", + " return _methods._mean(a, axis=axis, dtype=dtype,\n", + "/home/vscode/.local/lib/python3.10/site-packages/numpy/core/_methods.py:129: RuntimeWarning: invalid value encountered in scalar divide\n", + " ret = ret.dtype.type(ret / rcount)\n", + "/home/vscode/.local/lib/python3.10/site-packages/scipy/stats/_distn_infrastructure.py:2244: RuntimeWarning: invalid value encountered in multiply\n", " lower_bound = _a * scale + loc\n", - "/home/vscode/.local/lib/python3.10/site-packages/scipy/stats/_distn_infrastructure.py:2242: RuntimeWarning: invalid value encountered in multiply\n", - " upper_bound = _b * scale + loc\n" + "/home/vscode/.local/lib/python3.10/site-packages/scipy/stats/_distn_infrastructure.py:2245: RuntimeWarning: invalid value encountered in multiply\n", + " upper_bound = _b * scale + loc\n", + "/home/vscode/.local/lib/python3.10/site-packages/numpy/core/_methods.py:206: RuntimeWarning: Degrees of freedom <= 0 for slice\n", + " ret = _var(a, axis=axis, dtype=dtype, out=out, ddof=ddof,\n", + "/home/vscode/.local/lib/python3.10/site-packages/numpy/core/_methods.py:163: RuntimeWarning: invalid value encountered in divide\n", + " arrmean = um.true_divide(arrmean, div, out=arrmean,\n", + "/home/vscode/.local/lib/python3.10/site-packages/numpy/core/_methods.py:198: RuntimeWarning: invalid value encountered in scalar divide\n", + " ret = ret.dtype.type(ret / rcount)\n" ] } ], "source": [ "# computing average and 95% interval for failed and successful cases on all criteria\n", "try:\n", - " # convert the criteria to dict type if it is already not\n", - " dictionary_for_eval = eval(open(criteria_file, \"r\").read())\n", + " criteria = Criterion.parse_json_str(open(criteria_file, \"r\").read())\n", "except: # noqa: E722\n", " pass\n", "\n", - "criteria = list(dictionary_for_eval.keys())\n", + "\n", "nl2int = {}\n", - "for criterion in dictionary_for_eval:\n", + "for criterion in criteria:\n", " score = 0\n", - " for v in dictionary_for_eval[criterion][\"accepted_values\"]:\n", + " for v in criterion.accepted_values:\n", " nl2int[v] = score\n", " score += 1\n", "print(nl2int)\n", @@ -3522,17 +2701,17 @@ " try:\n", " tmp_dic = eval(outcome[game][\"estimated_performance\"])\n", " if outcome[game][\"actual_success\"] == \"false\":\n", - " task[\"f\"].append(nl2int[tmp_dic[criterion]])\n", + " task[\"f\"].append(nl2int[tmp_dic[criterion.name]])\n", " else:\n", - " task[\"s\"].append(nl2int[tmp_dic[criterion]])\n", + " task[\"s\"].append(nl2int[tmp_dic[criterion.name]])\n", " except: # noqa: E722\n", " pass\n", "\n", - " average_f[criterion] = np.mean(task[\"f\"])\n", - " average_s[criterion] = np.mean(task[\"s\"])\n", + " average_f[criterion.name] = np.mean(task[\"f\"])\n", + " average_s[criterion.name] = np.mean(task[\"s\"])\n", "\n", - " conf_interval_s[criterion] = stats.norm.interval(0.95, loc=np.mean(task[\"s\"]), scale=stats.sem(task[\"s\"]))\n", - " conf_interval_f[criterion] = stats.norm.interval(0.95, loc=np.mean(task[\"f\"]), scale=stats.sem(task[\"f\"]))" + " conf_interval_s[criterion.name] = stats.norm.interval(0.95, loc=np.mean(task[\"s\"]), scale=stats.sem(task[\"s\"]))\n", + " conf_interval_f[criterion.name] = stats.norm.interval(0.95, loc=np.mean(task[\"f\"]), scale=stats.sem(task[\"f\"]))" ] }, { @@ -3544,7 +2723,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 19, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -3554,9 +2733,17 @@ "outputId": "248cd0bc-0927-4d9f-b911-088bd76acf5d" }, "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/tmp/ipykernel_394256/2108490914.py:34: UserWarning: Tight layout not applied. The left and right margins cannot be made large enough to accommodate all axes decorations.\n", + " plt.tight_layout() # Adjust subplot parameters to fit the labels\n" + ] + }, { "data": { - "image/png": "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ybEPp/j1euDAAX333Xf2OpOq/8KFC+rdu7fy588vHx8fFS1aVO+9957DmVO395M0btw4FSlSRD4+Pvrzzz8lufcaxdfxyy+/qE+fPgoJCVFAQIAef/xxnT592mE97d69Wz///LO9Vnf7Oxo7dqzCwsLk5+enunXrateuXQ7P//777+rcubMKFy4sX19f5c6dW127dtXZs2cd2l2+fFm9e/dWwYIF5ePjo5w5c+rhhx/W9u3bHdpt3rxZTZo0UVBQkPz9/VW3bl398ssvSdZ5Z58z8a/d119/rWHDhilfvnzy9fXVQw89pP379zuN78583V0GV44dO6Zu3brZ9+1ChQrpxRdf1I0bNyRJ586dU9++fVW2bFllzpxZgYGBeuSRR7Rz506naU2YMEGlS5eWv7+/smXLpkqVKjkdX9w9XrkzrdsZYxQcHOxwCXBcXJyyZs0qLy8vh33xvffeU8aMGe2Xvd7Zh5PNZlNUVJRmzZpl3y7vPAPvwoUL6ty5s7JmzaqgoCB16dJFV69eTXhFS9q1a5cuXLigdu3aOczvscceU+bMmTV37lyX4127dk3Xr19P8DlfX1/7DxrZs2eXJHstixYt0o4dOxQREZFobXdOc8SIEQoPD9eoUaNcHgOfeeYZValSxf44qWOr5P62X7BgQfsPQyEhIQ7ve676cHL3GCq5tz/Fbw/79+936zX+/PPPVaVKFfu2WqdOHaczsX744QfVrl1bAQEBypIli5o2bardu3e7rDEp7taX1Hbszr4Y/5rNnTtXb731lvLmzSt/f3/7ZfmzZs1yqm/58uWy2WxaunSpJOnQoUN66aWXVKJECfn5+SlHjhx64oknHPq6S8i+ffvUunVr5c6dW76+vsqXL5/at2+vixcverTuXOncubMyZ86sw4cP2/fFvHnzauLEiZKkP/74Qw0aNFBAQIDCwsKcjkPuHCPXrFmjypUrS5K6dOni8J59uz///FP169eXv7+/8ubNq/fff9+tZbj9c2eJEiXk6+urihUrau3atQ7t4redP//8U0899ZSyZcumWrVqSbr1w9K7775rf88vWLCgBg4cmOC+tGLFCpUvX16+vr4qVaqUFixY4FatydkH//77b3Xo0EFBQUEKCQnR22+/LWOMjhw5ohYtWigwMFC5c+fW6NGjneaT3PcQIF0zAFJNeHi46datmzHGmLVr1xpJZsuWLfbnu3btarJmzWqio6Mdxps1a5aRZLZu3WqMMSY2NtY0atTI+Pv7m969e5spU6aYl19+2WTMmNG0aNHCYVxJpmTJkiYkJMRERESYiRMnmh07dhhjjMmXL5956aWXzEcffWTGjBljqlSpYiSZpUuXOkyjbdu2RpJ55plnzMSJE03btm1NuXLljCQzaNAge7sTJ06YfPnymfz585shQ4aYyZMnm+bNmxtJZuzYsUmunxo1aphcuXKZmzdvOgzv06ePkWT++ecfY4wxLVu2NG3btjUffPCBmTx5snniiSeMJNO3b1+H8Tp16mTCwsLsj3/66Scjyfz0008O7Q4cOGAkmRkzZtiH7dq1ywQFBZlSpUqZ9957z3z00UemTp06xmazmQULFtjbDRw40NhsNtO9e3fz6aefmtGjR5snn3zSjBw5MtFlPXHihMmVK5fJkiWLefPNN82YMWNMuXLlTIYMGezT/+eff8zs2bONJPPwww+b2bNnm9mzZye5HmNiYszp06fNsWPHzPLly014eLjJkiWLOXv2bKLjXb161RQvXtz4+vqa/v37m3HjxpmKFSuaBx54wGm93b5uT5w4YWbPnm2Cg4NN+fLl7XXu2rUrwfqjoqLMAw88YHLkyGEGDhxoPv74Y9OxY0djs9lMr169nF6bUqVKmcKFC5uRI0easWPHmkOHDrn9Gs2YMcNIMg8++KBp0KCBmTBhgnnttdeMl5eXadu2rb3dwoULTb58+Ux4eLi91hUrViS4vuJrK1u2rClYsKB57733TEREhMmePbsJCQkxJ06csLcdNWqUqV27thkyZIj55JNPTK9evYyfn5+pUqWKiYuLs7d76qmnTKZMmUyfPn3M1KlTzXvvvWeaNWtmPv/8c3ub1atXm0yZMpnq1aub0aNHm7Fjx5oHHnjAZMqUyWzevNlpuQ8cOGAfVrduXVO3bl374/h94sEHHzQVK1Y0Y8eONYMHDzb+/v6mSpUqDsvr7nzdWQZXjh07ZvLkyWM/rn388cfm7bffNiVLljTnz583xhizdetWU6RIEfPGG2+YKVOmmCFDhpi8efOaoKAgc+zYMfu0PvnkEyPJtGnTxkyZMsWMHz/edOvWzfTs2dPext3jlTvTcqV58+amYsWK9sc7duwwkkyGDBkcjrFNmzY1lSpVsj8eNGiQuf3j2uzZs42Pj4+pXbu2fbvcsGGDQ9sHH3zQtGrVykyaNMk8++yzRpLp379/ovVt2LDBSDLTp093ei4kJMT4+fmZ2NhY+zBJJiAgwNhsNvv7ypw5cxzGO3DggPHy8jKjRo0yBw8eNL179zZBQUEmKirKXL9+3RQuXNh89NFHidZ1pxUrVhhJZsiQIW61d+fYaoz72/7ChQvN448/biSZyZMnm9mzZ5udO3caY5z3p+QcQ93dn5LzGg8ePNhIMjVq1DAffPCBGT9+vHnqqafM66+/bm/z2WefGZvNZpo0aWImTJhg3nvvPVOwYEGTNWtWh2OFK/Hr7Jtvvkl2fYltx+7ui/HzL1WqlClfvrwZM2aMGTFihImKijKFCxc2jz76qFPNXbp0MdmyZTM3btwwxhjzzTffmHLlypl33nnHfPLJJ2bgwIEmW7ZsJiwszERFRTnNK/41i46ONoUKFTJ58uQxQ4cONVOnTjURERGmcuXK5uDBg4mut+To1KmT8fX1NaVKlTIvvPCCmThxoqlRo4b9M0qePHlMv379zIQJE0zp0qWNl5eX+ffff+3ju3OMPHHihBkyZIiRZJ577jn76xH/Gatu3bomT548Jn/+/KZXr15m0qRJpkGDBkaS+f7775NcBkmmTJkyJjg42AwZMsS89957JiwszPj5+Zk//vjD3i5+2ylVqpRp0aKFmTRpkpk4caJ9PcQfdydOnGg6duxoJJmWLVs6zCssLMwUL17cZM2a1bzxxhtmzJgxpmzZsiZDhgwO79+uPv8ldx8sX768efLJJ82kSZNM06ZNjSQzZswYU6JECfPiiy+aSZMmmZo1axpJ5ueff7aP7+l7CJBeETgBqWTbtm1Gklm5cqUxxpi4uDiTL18+hy/Xy5cvN5LMt99+6zDuo48+agoXLmx/PHv2bJMhQwazbt06h3Yff/yxkWR++eUX+7D4Lzi7d+92qunq1asOj2/cuGHKlCljGjRoYB/266+/Gkmmd+/eDm07d+7sFDh169bNhIaGmjNnzji0bd++vQkKCnKa350mTpxoJJnly5fbh8XGxpq8efOa6tWrJ1i3McY8//zzxt/f31y/ft0+7G4Cp4ceesiULVvWYXpxcXGmRo0aplixYvZh5cqVM02bNk10uVzp3bu3keTwGl6+fNkUKlTIFCxY0OmLXo8ePdye9saNG40k+1+JEiWcltmVcePGGUnm66+/tg+LiooyRYsWTTRwihcWFuZyXbiq/9133zUBAQHm77//dhj+xhtvGC8vL3P48GFjzP9em8DAQHPq1CmHtu6+RvHBS8OGDR3CnVdffdV4eXmZCxcu2IeVLl3a4QtkYuJr8/PzM0ePHrUP37x5s5FkXn31VfswV9vsl19+aSSZtWvX2ocFBQUl+lrHxcWZYsWKmcaNGzssy9WrV02hQoXMww8/7LTc7gROJUuWdAi6x48fbyTZvxwkZ75JLUNCOnbsaDJkyGAP1u9cbmOMuX79usO+Ycyt18HHx8chkGjRooUpXbp0ovNz93jlzrRc+eCDD4yXl5e5dOmSMcaYDz/80ISFhZkqVarYA4DY2FiTNWtWh23lzsDJGGMCAgJMp06dnOYR37Zr164Owx9//HGTI0eOROs7ffq0sdls9h9B4u3Zs8d+7Lh93dSoUcOMGzfOLF682EyePNmUKVPGSDKTJk1yudzx+8YXX3xhjDFm2LBhpkyZMk4/KCQlfltcuHChW+3dPba6u+0b87/1fPr0aYd53bk/uXsMTc7+5O5rvG/fPpMhQwbz+OOPO+0j8fO4fPmyyZo1q+nevbvD8ydOnDBBQUFOw++UWODkzjaY0Hbs7r4YP//ChQs7HVMHDBhgvL29zblz5+zDoqOjTdasWR1qc3Usjn/P/Oyzz5yWNf41iw+Mb1/2lBAftAwfPtw+7Pz588bPz8/YbDYzd+5c+/D4ffX2z2HuHiO3bt3q9LknXt26dZ3WR3R0tMmdO7dp3bp1kssQf/zYtm2bfdihQ4eMr6+vefzxx+3D4redJ5980mH83377zUgyzz77rMPwvn37Gknmxx9/tA8LCwszksz8+fPtwy5evGhCQ0PNgw8+aB925+vpyT743HPP2YfdvHnT5MuXz9hsNocfGONfq9u3c0/fQ4D0ikvqgFQyZ84c5cqVS/Xr15d065Tjdu3aae7cufZLwRo0aKDg4GB99dVX9vHOnz+vlStXql27dvZh33zzjUqWLKnw8HCdOXPG/tegQQNJ0k8//eQw77p167rsL8PPz89hPhcvXlTt2rUdLn2Jv/zupZdechj3zr49jDGaP3++mjVrJmOMQ12NGzfWxYsXk7ykpl27dvL29nY4zfjnn3/WsWPH7JfT3Vn35cuXdebMGdWuXVtXr151eUek5Dp37px+/PFHtW3b1j79M2fO6OzZs2rcuLH27dunY8eOSbrVp8fu3bu1b9++ZM3j+++/V5UqVeynj0tS5syZ9dxzz+ngwYP2S8Y8UapUKa1cuVKLFi1S//79FRAQ4NZd6r7//nuFhoaqTZs29mH+/v567rnnPK4lId98841q166tbNmyOWwrDRs2VGxsrNOp961bt1ZISIj9cXJeo3jPPfecw+U4tWvXVmxsrA4dOnRXy9KyZUvlzZvX/rhKlSqqWrWqvv/+e/uw27fZ69ev68yZM/b+tG7fL7JmzarNmzfr+PHjLuf122+/ad++fXrqqad09uxZ+3JHRUXpoYce0tq1a506c3dHly5dHPqjqV27tqRblyYld75JLYMrcXFxWrRokZo1a6ZKlSo5PR//uvn4+Nj7aomNjdXZs2eVOXNmlShRwmk9Hj16VFu3bnU5v+Qcr5KaVkLit68NGzZIktatW6fatWurdu3aWrdunaT/XdYWv7499cILLzjN++zZs7p06VKC4wQHB6tt27aaNWuWRo8erX///Vfr1q2zH4elW5ezxfvll1/Uq1cvNW/eXC+88IJ+/fVXlSlTRgMHDnRo17dvXx07dkwbN27UsWPH9OSTT+r48eMaMWKExo0bp5s3b+qVV15RgQIFVKVKlSQvBY1fhixZsri1LpJ7bE1q208Od4+hnuzHSb3GixYtUlxcnN555x2n/ozi95+VK1fqwoULevLJJx22eS8vL1WtWtXps0NyeLINSp59dujUqZPDMVW69fkhJibG4VKqFStW2C8bjXf7eDExMTp79qyKFi2qrFmzJvoZJSgoSNKtS/SSulzVCs8++6z9/6xZs6pEiRIKCAhQ27Zt7cNLlCihrFmzOmyr7h4jk5I5c2aH/toyZcqkKlWquL1fVK9eXRUrVrQ/LlCggFq0aKHly5c7dX9w57YT/955512JX3vtNUlyujw2T548evzxx+2PAwMD1bFjR+3YsUMnTpxwWZ8n++Dtr4mXl5cqVaokY4y6detmHx7/Wt2+njx9DwHSKwInIBXExsZq7ty5ql+/vg4cOKD9+/dr//79qlq1qk6ePKnVq1dLutVZbOvWrbV48WL7deoLFixQTEyMwwemffv2affu3QoJCXH4K168uKT/dXAar1ChQi7rWrp0qapVqyZfX19lz55dISEhmjx5skN/BIcOHVKGDBmcpnHn3fVOnz6t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QKe7+91p3rx5ql+/vqZNm+Yw/MKFC06/MgcEBKhdu3Zq166dbty4oVatWmnYsGEaMGBAso9XiU0r/o5srtSuXVvvvfeeVq1apeDgYIWHh8tms6l06dJat26d1q1bp8ceeyzJ+afkFy/p1i/38b/ex5+x1Lp16yTHiz9zLaHAzBijnj17qlevXvZt6Pjx48qTJ4+9TZ48eZwuf71drVq1lC1bNn355ZcaOHBggl/w4t3LY6urebtzDE2J/bhIkSKKi4vTn3/+meAX6Pj55syZ854eP+K52o6t+uwg3QqcIiIiNH/+fOXKlUuXLl1S+/btHdrMmzdPnTp1criT2PXr1926i6sklS1bVmXLltVbb72lDRs2qGbNmvr44481dOhQl+07duzocAZjQuGKVdw9Rqb0MSWh90V/f/8kA/b4z5379u2zn5koSSdPntSFCxec9uH4M55vX6a///5b0v/u0nune/1e6ul7CJAecUkdcI9du3ZNCxYs0GOPPaY2bdo4/b388su6fPmy/VbuGTJkUJs2bfTtt99q9uzZunnzpsPldNKtMwiOHTumTz/91OX8oqKikqzLy8tLNpvN4eyegwcPatGiRQ7t4s8emTRpksPwCRMmOE2vdevWmj9/vssvje6cRi7d+jD2+OOP6/vvv9fkyZMVEBCgFi1aOMxHcvz16MaNG071uRIWFiYvLy+n/oHuHDdnzpyqV6+epkyZosjIyESX5c7b2mfOnFlFixZN8Na98R599FFt2bJFGzdutA+LiorSJ598ooIFCyZ6WURCLly4oJiYGKfhU6dOlSSXfePcWdPx48c1b948+7CrV6/qk08+SXYtSWnbtq02btyo5cuXOz134cIF3bx5M9Hxk/MaJUdAQIDbXzziLVq0yOEL85YtW7R582Z7uOJqm5WkcePGOTyOjY11CgVz5sypPHny2LenihUrqkiRIho1apTLfrk8Xe6kuDtfd5bBlQwZMqhly5b69ttvtW3bNqfn49edl5eX03r85ptvnAKLO/fLTJkyqVSpUjLGKCYmJlnHq6SmlZjatWsrOjpa48aNU61atexfiGrXrq3Zs2fr+PHjbvXf5Ml26akBAwbo5s2bevXVV+3DXG1Xly9f1rhx4xQcHOzQV8vtZs6cqSNHjjj8aJIrVy57+BMTE6P9+/e7PNsmnr+/v15//XX99ddfev31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JF27dk1vvPGG8ufPb2/76aefJnu5d+3apW3btql169aJ9i01bNgw7d69W9OnT/fo+OHufnH8+HENGjRI1apVU86cOeXj46OCBQvqpZde0qlTp5ym27lzZ9lsNv37778aNWqUihcvLj8/P5UqVcoeyty4cUNvvvmmChYsKF9fXz3wwAP64YcfnKYV37/W9evX9cYbb6hAgQLy9fVVyZIlNWHCBBlj3F7eU6dO6dVXX1XRokXl4+Oj4OBgtW7dWrt27XJqu2/fPnXp0kWFChWSj4+PsmfPrnLlyql3795O82zatKn8/f01c+ZMt2sBgLSES+oAAHdl8uTJeumllxQaGqrHH39cOXLk0IkTJ7RlyxYtXLhQrVu39njae/fuVf369RUZGalatWqpZcuWioqK0u7duzV8+HD17fu/SwLnz5+vp556SsYYNWvWTCVKlNCpU6e0efNmTZs2Tc2aNbO3HTBggEaOHKm8efOqVatWCgoK0rp169SvXz9t3rxZ33zzjUPb999/X0WKFNFTTz2lLFmy6NixY1q/fr1WrVpl73TaivWwevVqSVK1atU8Xmcpwd11cO7cOfXu3Vu1a9fWo48+qmzZsunff//VkiVL9MMPP2jt2rWqXLmy0/QXLlyoFStWqGXLlqpZs6a+++47DR06VMYYBQUFaejQoWrRooXq1aun+fPnq3///sqVK5c6duzoNK0xY8Zo9erVateunZo2bapVq1Zp3Lhx2rRpk9auXStvb+9ElzU6OlpNmjTRmjVrVL58eXXr1k0xMTH67rvv1KJFC02YMEEvv/yyJMkYo8aNG2vz5s2qWbOmmjRpogwZMujQoUNasmSJnnnmGYWFhTlMv3r16lq1apU2bNigRo0aefiK4G7Fb7Px/eP07t1bkpQ1a1ZJnm/L7krOMSguLk7NmzfXqlWrVLZsWT311FM6e/asXn311UTPUnLFnWPM9u3bNWzYMA0ZMkSlSpVK9rIlZ79Yu3atRo8erYceekhVq1aVt7e3duzYocmTJ2v58uXavn27goKCnObRp08fbd68Wc2aNZOXl5fmzp2rp556StmyZdOECRP0559/qmnTprp+/bq++OILtWjRQn/99ZfLM0fbtm2rHTt22I/R8+fPV8+ePXXw4EGNHj06yeX9559/VK9ePR09elSNGjVSy5YtderUKc2fP1/Lly/X6tWrVbVqVUm3ArYqVaooKipKTZs2Vbt27RQVFaV9+/Zp0qRJGjVqlDJm/N/Xs0yZMqlixYrauHGjoqKiuKwOQLpD4AQAuCtTp05VpkyZ9NtvvylnzpwOz509e/aupt2hQwdFRkbqk08+Uffu3R2eO3r0qP3/kydPqlOnTvL29ta6dev04IMPJth25cqVGjlypBo3bqz58+fbP8AbY/TSSy/p448/1vz58+1fPqZOnao8efLo999/l7+/v8N0z507Z//fivXwyy+/KEOGDCpfvrxb7e8Vd9dBtmzZdPjwYeXNm9ehze7du1WtWjUNHDhQK1eudJr+Dz/8oF9++cX+BT4iIkJFixbV2LFjFRgYqB07dqhw4cKSpL59+6po0aIaNWqUy8Bp+fLl2rp1qx544AFJt17XDh066IsvvtCHH36o1157LdFlHTJkiNasWaO3335bERER9rNALl++rAYNGui1115Tq1atlCdPHu3atUubN29Wy5YttXDhQofpREdHKyYmxmn6lSpVknTrtSZwSj316tVTvXr17GeO3HlmkqfbsjuSewz67LPPtGrVKjVp0kRLly61n3HUq1cv+/bkrl9++UWSVLFiRZfPR0dHq2PHjipfvrz69+/v0fIlZ79o0KCBTpw4ocyZMzu0++yzz9SpUyd99NFHevPNN53m8ddff+n3339XSEiIJKlLly6qWrWq2rdvrzJlyuiPP/6wr9fGjRurXbt2Gj9+vD788EOnaf3999/atWuXPdiKiIhQ1apVNXbsWD355JNJruOOHTsqMjJSy5YtU+PGje3D33rrLVWqVEndu3fX77//LulWmHXhwgWNGzdOvXr1cpjOuXPnHMKmeJUqVdK6deu0ZcuWZAeMAJDauKQOAHDXvL29XZ45kiNHDo+nuWXLFm3btk116tRxCpskKV++fPb/Z82apaioKL322mtOYdOdbT/66CNJ0ieffOLwa7HNZtPIkSNls9n05ZdfOoyfKVMml5eVZM+e3eHx3a6Ho0ePKmvWrPLx8XGr/b3kzjrw8fFx+oIuSaVLl1b9+vW1du1alyFMhw4dHM4WyZIlix577DFdvXpVL774oj1skqT8+fOrVq1a+vPPP3Xz5k2naXXs2NEeNkm3Xtfhw4fLy8sryctS4uLiNHnyZBUpUsQhbIqv6Z133tGNGze0YMECh/H8/PycpuXj4+P0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e998s2fP1pQpU1SnTh0VKVJEgYGB+vPPP/X9998re/bs6tKli8O0//nnHx04cEAvvvii9QsOAPcAgRMA4K6MGDFCy5Yt05YtW/Ttt98qICBARYoU0eTJk9WtWzeHtu+++64yZcqkadOm6eOPP1bBggX19ttvq1mzZpo/f77TtIsVK6bt27drxIgR+vbbbzVu3DhlzpxZxYoV01tvveXQ9vHHH9fmzZs1YsQI/fzzz1qyZImCg4NVvnx5de/e3aHtkCFDVKdOHX344YdavXq1Lly4oBw5cqhQoUIaPHiwnn76aUlSQECA3nvvPa1evVrr1q3TqVOnlC1bNpUoUUIjRoxQ+/btPVoPCSlYsKAaN26sefPmacKECfYgJ96yZcscfi2XZO8/KX782wOn/fv3O93W+59//rGHe2FhYUkGTslZB4899pjmzZun4cOH6/PPP5e/v78aNGighQsXasiQIW6tg7vVp08fNW/eXOPGjdP+/fuVPXt29erVy77tJcXHx0c//PCDpk2bps8++0zz589XdHS0cuXKpVKlSumFF16wX9pSqVIlvf7661qzZo2+++47XbhwQblz51bDhg3Vr18/VatWzWHaa9eu1eHDhzVu3LiUWHRYKKW3ZXePQdKtYGbx4sWKiIjQnDlzNH78eBUpUkRjx45VsWLFkhU4Zc6cWR06dNAnn3yiyMhIj+6yl5Tk7BcjRoxQ9uzZNXPmTE2aNEm5cuXSk08+edd3ykuOr7/+WoMGDdKXX36pkydPqlChQvrwww/d7pOpUKFC2rFjh8aMGaNFixZpxowZ8vLyUmhoqOrUqaM2bdrY2z755JO6fv26fvnlF23ZskXR0dHKly+fXnzxRfXr18+pL6nPP/9ckvT8889bt8AAcA/ZzJ3n//8HXLp0SUFBQbp48eJd35YbAJJy/fp1HThwQIUKFXLrsikgMatXr1bDhg31+eefO3zpROIGDx6siIgI/fTTT6pXr15ql+NShw4d9MMPP+iff/6xd3QP3Gt79+5VmTJlNHjwYL355pupXU6qqVevnn7++WenS6HTips3b6pYsWIqVKiQfvzxxyTb81kEwL3kbuZCH04AAKQhDz30kJo0aaKhQ4cqLi4utcuBRf7++2/NnTtXb731FmETUlWJEiX07LPPauzYsbp8+XJql4MEzJo1S4cOHUryLFQASMu4pA4A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AAAAAMA95lGn4QEBATp9+nSS7Q4cOKDs2bMne/o///yzevToocqVK+vmzZsaOHCgGjVqpD///FMBAQGelAwAANxw/tRJnT990mFY9PXr9v8P/LVLPr6+TuNlC8mlbDlzpXh9AAAASB88CpzKli2rX3/9VWfOnFFwcLDLNocOHdLOnTv18MMPJ3v6y5Ytc3g8c+ZM5cyZU7/++qvq1KnjSckAAMANK76ara8njknw+beebulyeNsefdTuFec+HQEAAHB/8ihw6tChg9auXatnn31WX3zxhfz9/R2ev3Hjhl566SXFxMSoQ4cOd13kxYsXJSnBs6Wio6MVHR1tf+zJZXwAAEBq1O4ZVW7QKNnjZQvh7CYAAAD8j0eBU5cuXTRnzhwtWbJE4eHhatKkiSRp586d6tmzp5YsWaLDhw+rYcOGateu3V0VGBcXp969e6tmzZoqU6aMyzYjRoxQRETEXc0HAABI2XJyaRwAAADunkedhnt5eenbb7/Vk08+qWPHjmnq1KmSpB07duijjz7S4cOH1bp1ay1YsOCuC+zRo4d27dqluXPnJthmwIABunjxov3vyJEjdz1fAAAAAAAAeMajM5wkKXPmzJozZ47efvttff/99/r3338VFxen/Pnz65FHHlH58uXvuriXX35ZS5cu1dq1a5UvX74E2/n4+MjHx+eu5wcAAAAAAIC753HgFC88PFzh4eFW1GJnjNErr7yihQsXas2aNSpUqJCl0wcAAAAAAEDK8eiSum+//VZxcXFW12LXo0cPff755/riiy+UJUsWnThxQidOnNC1a9dSbJ4AAAAAAACwhkeBU4sWLZQ/f369/vrr+uuvv6yuSZMnT9bFixdVr149hYaG2v+++uory+cFAAAAAAAAa3kUOFWoUEGRkZH64IMPVKZMGdWoUUOffvqpLl26ZElRxhiXf507d7Zk+gAAAAAAAEg5HgVO27Zt0++//67evXsrODhYmzZt0gsvvKDQ0FB17NhRP/74o9V1AgAAAAAAIJ3wKHCSpDJlymjMmDE6duyYFixYoMcee0wxMTH6/PPP9fDDD6tQoUIaMmSIDh06ZGW9AAAAAAAASOM8DpziZcyYUS1bttTixYt17NgxjRo1SqVKldKhQ4cUERGhokWLWlEnAAAAAAAA0om7DpxuFxISoj59+mjLli3q1auXjDEpejc7AAAA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nUgmcvPmTV599VXCw8Np2LAha9euJU+ePLZuloiIiIiIiMhfohFOIpmIh4cHixcvpk6dOjRv3lxhk4iIiIiIiGRJGuEkkgklJSXh7OwMYF2xTkRERERERCSrUOAkkokpbBIREREREZGsSFPqRDIxhU0iIiIiIiKSFSlwEhERERERERGRDKXASUREREREREREMpQCJxERERERERERyVAKnEREREREREREJEMpcBIRERERERERkQylwElERERERERERDKUAicREREREREREclQCpxEREQkW9q+fTu9evWiTJkyuLq64uzsjKenJ02aNGHGjBn88ssvf+l8ly5dwmQy4e3t/fc0+Cnt3r0bk8lEgwYNbN0UERERyUYUOImIiEi2cuPGDZo0aULTpk1ZsmQJKSkp+Pn5ERgYSPny5Tlw4ADDhg2jRIkSHDp0KEPe09vbG5PJxKVLlzLkfCIiIiKZnYOtGyAiIiLyrCQkJFCnTh3OnDlDuXLlmD9/PnXr1k23T1JSEkuXLuW9994jNjb2T5+7SJEiREdH4+jomNHN/p9Ur16d6OhocuXKZeumiIiISDZiMgzDsHUjRERERJ6FHj16EBYWhre3N0ePHsXDw+M397127Rq3bt2ibNmy//P7ent7c/nyZS5evJjpptyJiIiI/B00pU5ERESyhQsXLrB8+XIAQkNDfzdsAihUqJA1bBo/fjwmk4nx48fz73//m9dee42iRYvi6OjIq6++Cjy5htOSJUswmUxcvnwZAB8fH0wmk/W/3bt3p3vPmJgYhg0bRvny5cmVKxd58uThpZdeYs6cOaSmpj7WxldffRWTycSSJUuIioqic+fOeHp6Ym9vz/jx44Hfr+G0Y8cOBg0aRJUqVcifPz/Ozs54eXnRuXNnIiIi/sTfqoiIiMiTaUqdiIiIZAvh4eGkpaXh5uZG69atn+oc586do2rVqjg5OVG7dm0MwyB//vy/uX+pUqXo2bMnX331FXfv3iUwMBAXFxfr9sKFC1t/3rt3L23btiU+Ph5vb2+aNGlCUlIShw8fZtCgQWzYsIHw8PAnTtk7cOAAAwYMwNPTk3r16pGYmEiePHn+sD8DBgzgypUrVKxYkdq1a+Pg4MDp06dZtWoVa9as4csvvyQwMPAv/i2JiIiIKHASERGRbOLIkSMAvPDCC9jb2z/VOZYvX063bt1YuHAhzs7Of7h/nTp1qFOnDrt37+bu3btMmzbtiVPqrl69Svv27bl16xbz5s2jf//+2Nk9GIgeFxdHp06d2LZtG5MnT2bcuHGPHb9gwQJGjRrFpEmTrMf9GdOmTaN+/fq4u7une33dunV07NiR/v374+/vT86cOf/0OUVERERAU+pEREQkm/jll18AKFiw4FOfw8PDgzlz5vypsOmvmDlzJnFxcbz55pu8/vrr6UKjfPnysWzZMhwdHZkzZw5PKr9ZpkwZJk6c+JfCJoC2bds+FjZZXu/YsSNxcXHs2rXrr3dIREREsj2NcBIRERH5kxo3bkzevHkz/LwbN24EoHPnzk/cXqRIEUqXLs2PP/7IuXPnKFOmTLrtbdu2fepRWzExMWzcuJHTp0+TkJBgrRV16tQpAM6cOYO/v/9TnVtERESyLwVOIiIiki0UKFAAgOvXrz/1Of6uFeYuXLgAQN26df9w319++eWxwOlp2/X+++8zadIkUlJSfnOf27dvP9W5RUREJHtT4CQiIiLZQrVq1QgLC+PYsWOkpaU91Yigv6uWkdlsBqBDhw7kzp37d/fNly9fhrRrzZo1jB8/HhcXF+bMmUPDhg157rnnyJkzJyaTidGjRzN58uQnTuETERER+SMKnERERCRbaNmyJcOGDePWrVusX7+edu3a2bpJVkWLFuXcuXOMHDmSF1988Zm856pVqwCYNGkS/fr1e2z7uXPnnkk7RERE5J9JRcNFREQkWyhZsiRBQUEADB8+nJs3b/7u/tevX+fMmTMZ8t5OTk4A1vpIj2rRogXw3xDoWbD0v3jx4o9tu379Otu3b39mbREREZF/HgVOIiIikm3861//olSpUly8eJE6deqwb9++x/ZJTk5m0aJFVK1alejo6Ax5Xy8vL+C/hbgfFRwcjJubG6GhoUyfPp3k5OTH9rl48SKff/55hrQHoHz58gDMnz8/3fslJCTQs2dPEhISMuy9REREJPvRlDoRERHJNtzd3dm/fz+dO3dm9+7d1K1bFx8fHypVqkSuXLm4du0ahw8f5tdff8XV1ZXnnnsuQ943MDCQXbt20a1bN5o2bYq7uzvwIGgqW7YsXl5efPPNNwQGBjJixAimTJmCr68vnp6eJCQkEB0dzfnz53n55Zfp1q1bhrRp6NChLFu2jE2bNlGiRAlq1KhBSkoKe/bsIVeuXPTu3ZtFixZlyHuJiIhI9qPASURERLKVggULsmvXLrZs2cKKFSs4cOAAO3fuJCkpiXz58lGzZk0CAgLo3r07Hh4eGfKer7/+Onfu3OHzzz9n06ZN3L9/H4Bu3bpRtmxZAOrVq8epU6eYM2cOGzduJCIigqSkJAoWLEixYsXo1q0bgYGBGdIeAB8fH44fP86YMWP47rvvCA8Pp3DhwgQFBTF+/Hg+/vjjDHsvERERyX5MhpYeERERERERERGRDKQaTiIiIiIiIiIikqEUOImIiIiIiIiISIZS4CQiIiIiIiIiIhlKgZOIiIiIiIiIiGQoBU4iIiIiIiIiIpKhFDiJiIiIiIiIiEiGUuAkIiIiIiIiIiIZSoGTiIiIiIiIiIhkKAVOIiIiIiIiIiKSoRQ4iYiIiIiIiIhIhlLgJCIiIiIiIiIiGUqBk4iIiIiIiIiIZCgFTiIiIiIiIiIikqH+Dx3bsMPfNWoAAAAAAElFTkSuQmCC", + "image/png": "iVBORw0KGgoAAAANSUhEUgAACO8AAAmyCAYAAACFOzwGAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/H5lhTAAAACXBIWXMAAA9hAAAPYQGoP6dpAAEAAElEQVR4nOzdd5gV5f034M/SdpHeVFQERRQLib3SrGhU7L1r1ESNGoPGkgSwm1gwJmKLjRAbKiqJsaNgAXuNRqNYEBsKCCIizPuHL/tz3QUWFA+6931de117Zp6Z+c6cOc85cD77PGVFURQBAAAAAAAAAAC+d/VKXQAAAAAAAAAAANRVwjsAAAAAAAAAAFAiwjsAAAAAAAAAAFAiwjsAAAAAAAAAAFAiwjsAAAAAAAAAAFAiwjsAAAAAAAAAAFAiwjsAAAAAAAAAAFAiwjsAAAAAAAAAAFAiwjsAAAAAAAAAAFAiwjsAAAC1cOCBB6ZTp06lLuNbe//997PrrrumTZs2KSsry6BBg77X448cOTJlZWUZOXJk5bKaru3UqVPz85//PEsvvXTKyspy7LHHJil9/YvKuHHjUlZWlnPPPbfUpdTo6quvTllZWcaNG1e5rHfv3undu3fJamL+BgwYkLKysgVq+9FHHy3iqhgyZEi6du2ahg0bpmXLlklq/3qqqQ9l8VaXn7PevXtnjTXWKHUZtdKpU6dst912821Xl59PAABg0RLeAQCAH7CLL744ZWVl2WCDDUpdymLjqaeeSllZWX73u9/Ntc2rr76asrKyHHfccd9jZYuHX//617nrrrty0kknZciQIdl6663n2XbttddO69ats8QSS2TVVVfNgAEDMnXq1EVe55lnnpmrr746v/zlLzNkyJDst99+C1x/qV188cW5+uqrS10GVHHmmWdm+PDhi2Tf119/fdZee+1UVFSkXbt2OeSQQ2oMA5WVldX4c/bZZ1dp9/DDD2fttddOs2bN0rt377z88svV9nX00UenT58+C1zrrbfemm222SZt27ZNo0aNsswyy2T33XfP/fffv8D7WhAvv/xyDjzwwHTu3DmXX355LrvsskV6vMXVnADEsGHDFmr7RXkfs2DefffdDBgwIM8880ypSwEAAPhBa1DqAgAAgIU3dOjQdOrUKWPHjs1rr72WlVZaqdQlldzaa6+drl275rrrrsvpp59eY5t//OMfSZJ99933+yxtsXD//fdnhx12SL9+/ebb9vHHH0+PHj1y0EEHpaKiIk8//XTOPvvs3HvvvXnooYdSr9538/cgl19+eWbPnl2tzg033DD9+/df6PpL7eKLL07btm1z4IEHlrqUReLuu+8udQnMx+9+97uceOKJVZadeeaZ2XXXXbPjjjt+p8caPHhwjjjiiGy++eY5//zz88477+TCCy/ME088kTFjxqSioqJK+y233DL7779/lWVrrbVW5e+TJ0/ODjvskA033DCHHXZYrr766uyyyy557rnnUr9+/STJiy++mMsvvzxPPvlkressiiIHH3xwrr766qy11lo57rjjsvTSS2fChAm59dZbs/nmm+fhhx/Oxhtv/C2uxtyNHDkys2fPzoUXXljlPdvracEsqvuYBffuu+9m4MCB6dSpU9Zcc81SlwMAAPCDJbwDAAA/UG+88UYeeeSR3HLLLTn88MMzdOjQakGHRW327Nn54osvqn0pW2r77LNPfv/73+exxx7LhhtuWG39ddddl65du2bttdcuQXWl9cEHH1RO0zI/o0ePrrasc+fO6devX8aOHVvjtV0YDRs2rLbsgw8+yGqrrVbj8trWXxtffvllZs+enUaNGn1n+6wrXLPFX4MGDdKgwaL/r58vvvgiJ598cnr27Jl77rmncqqujTfeONtvv30uv/zy/OpXv6qyzcorrzzPAOWjjz6a6dOnZ9iwYamoqMjWW2+dFVZYIa+99lpWWWWVJMmxxx6bQw89tMa+Ym7OO++8XH311Tn22GNz/vnnV5lW7JRTTsmQIUMW6TX74IMPkqRaP+b1VHqff/55GjVq9J0FUymtadOmpUmTJqUuAwAAoNb8axQAAH6ghg4dmlatWmXbbbfNrrvumqFDh1aumzlzZlq3bp2DDjqo2nZTpkxJRUVFlZFLZsyYkf79+2ellVZKeXl5OnTokBNOOCEzZsyosm1ZWVmOOuqoDB06NKuvvnrKy8vz73//O0ly7rnnZuONN06bNm3SuHHjrLPOOjVOhzF9+vQcffTRadu2bZo1a5a+fftm/PjxKSsry4ABA6q0HT9+fA4++OAstdRSKS8vz+qrr54rr7xyvtdmn332SfJ/I+x83ZNPPplXXnmlss1tt92WbbfdNssss0zKy8vTuXPnnHbaaZk1a9Y8jzFnyo+RI0dWWT5u3LiUlZVVmy7p5Zdfzq677prWrVunoqIi6667bm6//fYqbWbOnJmBAwemS5cuqaioSJs2bdK9e/fcc8898z3n119/PbvttlvlFFcbbrhh/vnPf1auv/rqq1NWVpaiKPLXv/61cpqaBdWpU6ckyaRJk+bb9p133smOO+6YJk2aZMkll8yvf/3ravdUkhx44IGV+51zXd94443885//rKxzfvVPmjQpxx57bDp06JDy8vKstNJKOeecc6qM6DPnuTn33HMzaNCgdO7cOeXl5XnppZeS1O45mlPHww8/nOOOOy7t2rVLkyZNstNOO+XDDz+scp1efPHFPPjgg5W19u7du1bX+IILLkjHjh3TuHHj9OrVKy+88EKV9c8991wOPPDArLjiiqmoqMjSSy+dgw8+OBMnTqzS7tNPP82xxx6bTp06pby8PEsuuWS23HLLPPXUU1XajRkzJltvvXVatGiRJZZYIr169crDDz883zp79+5d5ZzmPHc33nhjzjjjjCy33HKpqKjI5ptvntdee63a9rU5bm3PoSbjx4/PIYccUvnaXmGFFfLLX/4yX3zxRZLk448/Tr9+/dKtW7c0bdo0zZs3zzbbbJNnn3222r4uuuiirL766lliiSXSqlWrrLvuutX6l9r2V7XZ19cVRZG2bdtWmeZv9uzZadmyZerXr1/ltXjOOeekQYMGlVPbDRgwoMrrpKysLNOmTcs111xTeV9+c2SoSZMm5cADD0zLli3TokWLHHTQQfnss8/mfqGTvPDCC5k0aVL22GOPKsfbbrvt0rRp01x//fU1bjd9+vR8/vnnc11XUVFRGQ5t3bp1klTWMnz48Dz99NMZOHDgPGv75j7POuusdO3aNeeee26NfeB+++2X9ddfv/Lx/PrWpPb3fqdOnSpDtu3atavyvvfN11NS+z40qd3rac798Nprr9XqOf773/+e9ddfv/Je7dmzZ7URgu6888706NEjTZo0SbNmzbLtttvmxRdfrLHG+altffO7j2vzWpzznF1//fX53e9+l2WXXTZLLLFE5dSb11xzTbX67rrrrpSVlWXEiBFJkjfffDNHHHFEVllllTRu3Dht2rTJbrvtlnHjxs33XF999dXssssuWXrppVNRUZHlllsue+65ZyZPnrxQ164mBx54YJo2bZq33nqr8rW47LLL5q9//WuS5Pnnn89mm22WJk2apGPHjtX6odr0kSNHjsx6662XJDnooIOqvGd/3UsvvZRNN900SyyxRJZddtn88Y9/rNU5fP1z5yqrrJKKioqss846eeihh6q0m3PvvPTSS9l7773TqlWrdO/ePclXId3TTjut8j2/U6dOOfnkk+f6Wrr77ruz5pprpqKiIquttlpuueWWWtW6IK/B//73v9l3333TokWLtGvXLr///e9TFEXefvvt7LDDDmnevHmWXnrpnHfeedWOs6DvIQAAwA+HkXcAAOAHaujQodl5553TqFGj7LXXXhk8eHAef/zxrLfeemnYsGF22mmn3HLLLbn00kur/EX/8OHDM2PGjOy5555JvvoSuG/fvhk9enQOO+ywrLrqqnn++edzwQUX5L///W+GDx9e5bj3339/brzxxhx11FFp27ZtZejiwgsvTN++fbPPPvvkiy++yPXXX5/ddtstI0aMyLbbblu5/YEHHpgbb7wx++23XzbccMM8+OCDVdbP8f7772fDDTes/OKmXbt2ufPOO3PIIYdkypQpOfbYY+d6bVZYYYVsvPHGufHGG3PBBRdUTrGS/F+gZ++9907yVRijadOmOe6449K0adPcf//9+cMf/pApU6bkT3/60wI9J3Pz4osvZpNNNsmyyy6bE088MU2aNMmNN96YHXfcMTfffHN22mmnJF99qXPWWWfl5z//edZff/1MmTIlTzzxRJ566qlsueWWc93/+++/n4033jifffZZjj766LRp0ybXXHNN+vbtm2HDhmWnnXZKz549M2TIkOy33341TlczN19++WUmTZqUL774Ii+88EJ+97vfpVmzZlW+3K7J9OnTs/nmm+ett97K0UcfnWWWWSZDhgzJ/fffP8/tVl111QwZMiS//vWvs9xyy+U3v/lNkq+m05lb/Z999ll69eqV8ePH5/DDD8/yyy+fRx55JCeddFImTJiQQYMGVTnGVVddlc8//zyHHXZYysvL07p161o/R3P86le/SqtWrdK/f/+MGzcugwYNylFHHZUbbrghSTJo0KD86le/StOmTXPKKackSZZaaqn5Xu9rr702n376aY488sh8/vnnufDCC7PZZpvl+eefr9z+nnvuyeuvv56DDjooSy+9dF588cVcdtllefHFF/PYY49VBhJ+8YtfZNiwYTnqqKOy2mqrZeLEiRk9enT+85//VI46df/992ebbbbJOuusk/79+6devXq56qqrstlmm2XUqFHzfZ5rcvbZZ6devXrp169fJk+enD/+8Y/ZZ599MmbMmMo2tT1ubc6hJu+++27WX3/9TJo0KYcddli6du2a8ePHZ9iwYfnss8/SqFGjvP766xk+fHh22223rLDCCnn//fdz6aWXplevXnnppZeyzDLLJPlqWrejjz46u+66a4455ph8/vnnee655zJmzJjKfqS2/VVt9vVNZWVl2WSTTap8Wf3cc89l8uTJqVevXh5++OHKPnTUqFFZa6210rRp0xr3NWTIkMr+5bDDDkvy1WhaX7f77rtnhRVWyFlnnZWnnnoqV1xxRZZccsmcc845c73ec74Eb9y4cbV1jRs3ztNPP53Zs2dXGdHk6quvzsUXX5yiKLLqqqvmd7/7XZVrsNZaa2Xy5Mk577zzsuuuu2bQoEFp0aJFVllllcyYMSO/+c1vMnDgwLRq1WqudX3T6NGj8/HHH+fYY4+t8r4wN7XpW79ufvf+oEGDcu211+bWW2/N4MGD07Rp0/zkJz+p8dgL0ocu6Ou4Ns/xwIEDM2DAgGy88cY59dRT06hRo4wZMyb3339/ttpqqyRf3U8HHHBA+vTpk3POOSefffZZBg8enO7du+fpp5+u/HywoOZX37zu4wX97HDaaaelUaNG6devX2bMmJHVVlstK664Ym688cYccMABVdrecMMNadWqVfr06ZPkq6klH3nkkey5555ZbrnlMm7cuAwePDi9e/fOSy+9lCWWWKLG8/viiy/Sp0+fzJgxI7/61a+y9NJLZ/z48RkxYkQmTZqUFi1aLNR1q8msWbOyzTbbpGfPnvnjH/+YoUOH5qijjkqTJk1yyimnZJ999snOO++cSy65JPvvv3822mijrLDCCklSqz5y1VVXzamnnpo//OEPOeyww9KjR48kqTLt3CeffJKtt946O++8c3bfffcMGzYsv/3tb9OtW7dss8028z2HBx98MDfccEOOPvrolJeX5+KLL87WW2+dsWPHZo011qjSdrfddkuXLl1y5plnpiiKJMnPf/7zXHPNNdl1113zm9/8JmPGjMlZZ52V//znP7n11lurbP/qq69mjz32yC9+8YsccMABueqqq7Lbbrvl3//+9zw/hy3oa3CPPfbIqquumrPPPjv//Oc/c/rpp6d169a59NJLs9lmm+Wcc87J0KFD069fv6y33nrp2bNnkoV7DwEAAH5ACgAA4AfniSeeKJIU99xzT1EURTF79uxiueWWK4455pjKNnfddVeRpLjjjjuqbPuzn/2sWHHFFSsfDxkypKhXr14xatSoKu0uueSSIknx8MMPVy5LUtSrV6948cUXq9X02WefVXn8xRdfFGussUax2WabVS578skniyTFscceW6XtgQceWCQp+vfvX7nskEMOKdq3b1989NFHVdruueeeRYsWLaod75v++te/FkmKu+66q3LZrFmzimWXXbbYaKON5lp3URTF4YcfXiyxxBLF559/XrnsgAMOKDp27Fj5+IEHHiiSFA888ECVbd94440iSXHVVVdVLtt8882Lbt26Vdnf7Nmzi4033rjo0qVL5bKf/vSnxbbbbjvP86rJscceWySp8hx++umnxQorrFB06tSpmDVrVuXyJMWRRx5Z630/+uijRZLKn1VWWaXaOddk0KBBRZLixhtvrFw2bdq0YqWVVqp23b55bYuiKDp27Fjjtaip/tNOO61o0qRJ8d///rfK8hNPPLGoX79+8dZbbxVF8X/PTfPmzYsPPvigStvaPkdXXXVVkaTYYostitmzZ1cu//Wvf13Ur1+/mDRpUuWy1VdfvejVq9dcrlBVc2pr3Lhx8c4771QuHzNmTJGk+PWvf125rKZ79rrrriuSFA899FDlshYtWszzuZ49e3bRpUuXok+fPlXO5bPPPitWWGGFYsstt6x23m+88Ublsl69elU5vzmviVVXXbWYMWNG5fILL7ywSFI8//zzC3zc+Z3D3Oy///5FvXr1iscff7zG8y6Kovj888+rvDaK4qvnoby8vDj11FMrl+2www7F6quvPs/j1ba/qs2+avKnP/2pqF+/fjFlypSiKIriz3/+c9GxY8di/fXXL377298WRfFV/9ayZcsq90r//v2Lb/7XT5MmTYoDDjig2jHmtD344IOrLN9pp52KNm3azLO+Dz/8sCgrKysOOeSQKstffvnlyr7j69dm4403LgYNGlTcdtttxeDBg4s11lijSFJcfPHFNZ73nNfGP/7xj6IoiuKMM84o1lhjjeLLL7+cZ13fNOdevPXWW2vVvrZ9a23v/aL4v+v84YcfVjnWN19Pte1DF+T1VNvn+NVXXy3q1atX7LTTTtVeI3OO8emnnxYtW7YsDj300Crr33vvvaJFixbVln/TnGt20003LXB9RTH3+7i2r8U5x19xxRWr9aknnXRS0bBhw+Ljjz+uXDZjxoyiZcuWVWqrqS+e85557bXXVjvXOc/Z008/Xe3cF4UDDjigSFKceeaZlcs++eSTonHjxkVZWVlx/fXXVy6f81r9+uew2vaRjz/+eLXPPXP06tWr2vWYMWNGsfTSSxe77LLLfM9hTv/xxBNPVC578803i4qKimKnnXaqXDbn3tlrr72qbP/MM88USYqf//znVZb369evSFLcf//9lcs6duxYJCluvvnmymWTJ08u2rdvX6y11lqVy775fC7Ma/Cwww6rXPbll18Wyy23XFFWVlacffbZlcvnPFdfv88X9j0EAAD4YTBtFgAA/AANHTo0Sy21VDbddNMkX43MsMcee+T666+vnO5ps802S9u2bStHAkm++uvne+65J3vssUflsptuuimrrrpqunbtmo8++qjyZ7PNNkuSPPDAA1WO3atXr6y22mrVavr6iAuffPJJJk+enB49elSZ3mbOFFtHHHFElW1/9atfVXlcFEVuvvnmbL/99imKokpdffr0yeTJk+c7bc4ee+yRhg0bVplK4MEHH8z48eMrp8z6Zt2ffvppPvroo/To0SOfffZZXn755XkeozY+/vjj3H///dl9990r9//RRx9l4sSJ6dOnT1599dWMHz8+SdKyZcu8+OKLefXVVxfoGP/617+y/vrrV04RkSRNmzbNYYcdlnHjxlVOC7UwVltttdxzzz0ZPnx4TjjhhDRp0qRySp751dS+ffvsuuuulcuWWGKJylESvks33XRTevTokVatWlW5V7bYYovMmjWr2vQau+yyS9q1a1f5eEGeozkOO+ywKlPu9OjRI7Nmzcqbb775rc5lxx13zLLLLlv5eP31188GG2yQf/3rX5XLvn7Pfv755/noo4+y4YYbJkmV10XLli0zZsyYvPvuuzUe65lnnsmrr76avffeOxMnTqw872nTpmXzzTfPQw89VGXasdo66KCDqoz2NWckhtdff32Bjzu/c6jJ7NmzM3z48Gy//fZZd911q62f87yVl5dXjgQza9asTJw4MU2bNs0qq6xS7Tq+8847efzxx2s83oL0V/Pb19zMub8eeeSRJF+NsNOjR4/06NEjo0aNSvJ/U1fNud4L6xe/+EW1Y0+cODFTpkyZ6zZt27bN7rvvnmuuuSbnnXdeXn/99YwaNaqyH06+GklmjocffjjHHHNM+vbtm1/84hd58skns8Yaa+Tkk0+u0q5fv34ZP358Hn300YwfPz577bVX3n333Zx11lkZNGhQvvzyy/zqV7/K8ssvn/XXX3++073NOYdmzZrV6losaN86v3t/QdS2D12Y1/H8nuPhw4dn9uzZ+cMf/lBltKTk/14/99xzTyZNmpS99tqryj1fv379bLDBBtU+OyyIhbkHk4X77HDAAQdUGzFqjz32yMyZM6tMl3T33XdXTg03x9e3mzlzZiZOnJiVVlopLVu2nOdnlDkj69x1113znZLuu/Dzn/+88veWLVtmlVVWSZMmTbL77rtXLl9llVXSsmXLKvdqbfvI+WnatGn23XffyseNGjXK+uuvX+vXxUYbbZR11lmn8vHyyy+fHXbYIXfddVe1KU6/ee/Mee/8+rSDSSpH9fvmFHjLLLNMlRG1mjdvnv333z9PP/103nvvvRrrW5jX4Nefk/r162fddddNURQ55JBDKpfPea6+fp0W9j0EAAD4YRDeAQCAH5hZs2bl+uuvz6abbpo33ngjr732Wl577bVssMEGef/993PfffclSRo0aJBddtklt912W+WUJrfccktmzpxZ5cunV199NS+++GLatWtX5WfllVdOknzwwQdVjj9nOoVvGjFiRDbccMNUVFSkdevWadeuXQYPHpzJkydXtnnzzTdTr169avtYaaWVqjz+8MMPM2nSpFx22WXV6jrooINqrOub2rRpkz59+uTWW2/N559/nuSrKbMaNGhQ5QurF198MTvttFNatGiR5s2bp127dpVfMn299oX12muvpSiK/P73v692Lv37969yLqeeemomTZqUlVdeOd26dcvxxx+f5557br7HePPNN7PKKqtUW77qqqtWrl9YzZs3zxZbbJEddtgh55xzTn7zm99khx12yLPPPjvfmlZaaaUqAZckNdb5bb366qv597//Xe36brHFFknmfw8vyHM0x/LLL1/l8Zypez755JNvdS5dunSptmzllVfOuHHjKh9//PHHOeaYY7LUUkulcePGadeuXeU5ff2e/eMf/5gXXnghHTp0yPrrr58BAwZU+RJwTkjsgAMOqHbeV1xxRWbMmLFQr4H5XZsFOe78zqEmH374YaZMmVJtOpVvmj17di644IJ06dIl5eXladu2bdq1a1c5JdUcv/3tb9O0adOsv/766dKlS4488sgqIZEF6a/mt6+5WXvttbPEEktUBnXmhHd69uyZJ554Ip9//nnluq8HTRbGwt7bl156aX72s5+lX79+6dy5c3r27Jlu3bpl++23T5K5TuWVfPVl/lFHHZVJkyblySefrLJuqaWWyoYbblhZx29/+9tsvvnm2XzzzXPaaaflvvvuyw033JAdd9wx2267bSZNmjTX4zRv3jzJV0HN2ljQvvW77Bdq24cuzOt4fnX+73//S7169WoM6n7zuJtttlm14959993zfY+el4W9jgvz2aGmzzQ//elP07Vr1yrh5xtuuCFt27atDDYnXwXS/vCHP6RDhw5V+pBJkybNs+9cYYUVctxxx+WKK65I27Zt06dPn/z1r3+db387derUvPfee5U/H3744TzbJ0lFRUWVsGryVXhoueWWq3ZvtWjRoso1rm0fOT81HatVq1a1fl3M7X3xs88+q3YNvvl8zvnc+c3PmUsvvXRatmxZ7TVc02tuzufhr78Pf9138Rps0aJFKioq0rZt22rLv36dFvY9BAAA+GFoUOoCAACABXP//fdnwoQJuf7663P99ddXWz906NBstdVWSZI999wzl156ae68887suOOOufHGG9O1a9f89Kc/rWw/e/bsdOvWLeeff36Nx+vQoUOVx9/8C/Xkqy+S+/btm549e+biiy9O+/bt07Bhw1x11VVVRr6prTl/obzvvvvmgAMOqLHNT37yk/nuZ999982IESMyYsSI9O3bNzfffHO22mqryi+yJk2alF69eqV58+Y59dRT07lz51RUVOSpp57Kb3/723mOOvLNL3fm+OZfgc/ZR79+/dKnT58at5nzpVLPnj3zv//9L7fddlvuvvvuXHHFFbngggtyySWXVPkr7VLaeeeds99+++X666+vch+V0uzZs7PlllvmhBNOqHH9nC/e5vjmPbwgz9Ec9evXr7FdURS1qvnb2H333fPII4/k+OOPz5prrpmmTZtm9uzZ2Xrrravcs7vvvnt69OiRW2+9NXfffXf+9Kc/5Zxzzsktt9ySbbbZprLtn/70p6y55po1HmtegYu5md+1WZDjzu8cvo0zzzwzv//973PwwQfntNNOS+vWrVOvXr0ce+yxVa7jqquumldeeSUjRozIv//979x88825+OKL84c//CEDBw5coP5qfvuam4YNG2aDDTbIQw89lNdeey3vvfdeevTokaWWWiozZ87MmDFjMmrUqHTt2rXaF/ULamHv7RYtWuS2227LW2+9lXHjxqVjx47p2LFjNt5447Rr1y4tW7ac5/Zz3ms+/vjjubZ57LHHMmzYsLzwwgtJkuuuuy6///3vs9FGG2WjjTbKpZdemhEjRlQZ5ePrunbtmiR5/vnns+OOO86znoVRin5hYV7H30Wdc447ZMiQLL300tXWN2iw8P/luLD1Lcxnh5o+0yRfjb5zxhln5KOPPkqzZs1y++23Z6+99qpyXr/61a9y1VVX5dhjj81GG22UFi1apKysLHvuued8Ry0777zzcuCBB1a+3x999NE566yz8thjj2W55ZarcZtzzz23Sj/RsWPHuQZK5pjbtazNNa5tHzk/3+frYm7P59w+s30XvqvXYG2u08K+hwAAAD8MwjsAAPADM3To0Cy55JL561//Wm3dLbfckltvvTWXXHJJGjdunJ49e6Z9+/a54YYb0r1799x///055ZRTqmzTuXPnPPvss9l8880X+suNm2++ORUVFbnrrrtSXl5eufyqq66q0q5jx46ZPXt23njjjSp/Sf3aa69VadeuXbs0a9Yss2bNqhw9ZWH07ds3zZo1yz/+8Y80bNgwn3zySZUps0aOHJmJEyfmlltuSc+ePSuXv/HGG/Pd95yRAL45ysM3/4p7xRVXTPLVl++1OZfWrVvnoIMOykEHHZSpU6emZ8+eGTBgwDzDOx07dswrr7xSbfmcab86duw43+PW1owZMzJ79uz5/tV9x44d88ILL6Qoiir3VU11fludO3fO1KlTF/peWdDnqLYW5vVU05Rp//3vf9OpU6ckX408cd9992XgwIH5wx/+MM/tkqR9+/Y54ogjcsQRR+SDDz7I2muvnTPOOCPbbLNNOnfunOT/Rlf6vizoced1DjVp165dmjdvXhnwmJthw4Zl0003zd/+9rcqyydNmlRt9IMmTZpkjz32yB577JEvvvgiO++8c84444ycdNJJC9xfzWtfFRUVc92uR48eOeecc3Lvvfembdu26dq1a8rKyrL66qtn1KhRGTVqVLbbbrv5Hn9RfomdfDWixJxRJeaMpLPLLrvMd7s5IyrNLXxUFEWOPvroHHPMMZX30Lvvvptlllmmss0yyyxTbYq7r+vevXtatWqV6667LieffPJcvyyf4/vsW2s6dm360EXxOu7cuXNmz56dl156aa5hhDnHXXLJJb/X/mOOmu7j7+qzQ/JVeGfgwIG5+eabs9RSS2XKlCnZc889q7QZNmxYDjjggJx33nmVyz7//PN5jv70dd26dUu3bt3yu9/9Lo888kg22WSTXHLJJTn99NNrbL///vtXGVlrbkGV70pt+8hF3afM7X1xiSWWmG9Ycc7nzldffbVyxKwkef/99zNp0qRqr+E5I/F9/Zz++9//Jknl+/A3fd/vpQv7HgIAACz+TJsFAAA/INOnT88tt9yS7bbbLrvuumu1n6OOOiqffvppbr/99iRJvXr1suuuu+aOO+7IkCFD8uWXX1aZMiv5amSL8ePH5/LLL6/xeNOmTZtvXfXr109ZWVmVUWfGjRuX4cOHV2k3Z1STiy++uMryiy66qNr+dtlll9x88801fgFfm6kikq++2Nppp53yr3/9K4MHD06TJk2yww47VDlOUvWvmr/44otq9dWkY8eOqV+/fh566KEqy7+57ZJLLpnevXvn0ksvzYQJE+Z5LhMnTqyyrmnTpllppZUqpz2bm5/97GcZO3ZsHn300cpl06ZNy2WXXZZOnTrNc+qTuZk0aVJmzpxZbfkVV1yRJFl33XXnW9O7776bYcOGVS777LPPctllly1wLfOz++6759FHH81dd91Vbd2kSZPy5ZdfznP7BXmOFkSTJk1q/SXuHMOHD68SPhg7dmzGjBlTGVSp6Z5NkkGDBlV5PGvWrGoBqyWXXDLLLLNM5f20zjrrpHPnzjn33HMzderUarUs7HnPT22PW5tzqEm9evWy44475o477sgTTzxRbf2ca1e/fv1q1/Gmm26qFv745uuyUaNGWW211VIURWbOnLlA/dX89jUvPXr0yIwZMzJo0KB079698svlHj16ZMiQIXn33XfTo0ePee4jWbj7cmGddNJJ+fLLL/PrX/+6cllN99Wnn36aQYMGpW3btllnnXVq3NfVV1+dt99+u0oAdamllqoM0sycOTOvvfZajaPAzLHEEkvkt7/9bf7zn//kt7/9bY0jf/z973/P2LFjkyyavrW2atuHLorX8Y477ph69erl1FNPrTbCypxr1qdPnzRv3jxnnnlmjffuouo/5qjpPv6uPjskX41w0q1bt9xwww254YYb0r59+yoh3znH++Y9dNFFF1Ubge+bpkyZUu19qVu3bqlXr948+7YVV1wxW2yxReXPJptsUuvzWRi17SObNGmSpHqY+bvy6KOP5qmnnqp8/Pbbb+e2227LVlttNd8A3s9+9rMk1d8j54w2ue2221ZZ/u677+bWW2+tfDxlypRce+21WXPNNefat3yf76Xf5j0EAABY/Bl5BwAAfkBuv/32fPrpp+nbt2+N6zfccMO0a9cuQ4cOrQzp7LHHHrnooovSv3//dOvWrcpfHifJfvvtlxtvvDG/+MUv8sADD2STTTbJrFmz8vLLL+fGG2/MXXfdNd+gxrbbbpvzzz8/W2+9dfbee+988MEH+etf/5qVVlopzz33XGW7ddZZJ7vssksGDRqUiRMnZsMNN8yDDz5Y+VfNX/9L57PPPjsPPPBANthggxx66KFZbbXV8vHHH+epp57KvffeO8+pVb5u3333zbXXXpu77ror++yzT+WXTEmy8cYbp1WrVjnggANy9NFHp6ysLEOGDKnVVA4tWrTIbrvtlosuuihlZWXp3LlzRowYkQ8++KBa27/+9a/p3r17unXrlkMPPTQrrrhi3n///Tz66KN555138uyzzyZJVltttfTu3TvrrLNOWrdunSeeeCLDhg3LUUcdNc9aTjzxxFx33XXZZpttcvTRR6d169a55ppr8sYbb+Tmm29OvXoL/ncbI0eOzNFHH51dd901Xbp0yRdffJFRo0bllltuybrrrjvXaWnmOPTQQ/OXv/wl+++/f5588sm0b98+Q4YMyRJLLLHAtczP8ccfn9tvvz3bbbddDjzwwKyzzjqZNm1ann/++QwbNizjxo2rNpLKN9X2OVoQ66yzTgYPHpzTTz89K620UpZccslsttlm89xmpZVWSvfu3fPLX/6yMqjRpk2byinBmjdvnp49e+aPf/xjZs6cmWWXXTZ33313tdGiPv300yy33HLZdddd89Of/jRNmzbNvffem8cff7xylIh69erliiuuyDbbbJPVV189Bx10UJZddtmMHz8+DzzwQJo3b5477rhjgc97fmp73Nqcw9yceeaZufvuu9OrV68cdthhWXXVVTNhwoTcdNNNGT16dFq2bJntttsup556ag466KBsvPHGef755zN06NDKkZjm2GqrrbL00ktnk002yVJLLZX//Oc/+ctf/pJtt902zZo1S1L7/qo2+5qbjTbaKA0aNMgrr7ySww47rHJ5z549M3jw4CSpVXhnnXXWyb333pvzzz8/yyyzTFZYYYVssMEG891ufs4+++y88MIL2WCDDdKgQYMMHz48d999d04//fSst956le3++te/Zvjw4dl+++2z/PLLZ8KECbnyyivz1ltvZciQIWnUqFG1fX/66ac5+eSTc+aZZ1a5TrvuumtlwOThhx/O559/Xvll/dwcf/zxefHFF3PeeeflgQceyK677pqll1467733XoYPH56xY8fmkUceSbJo+tbaqm0fuihexyuttFJOOeWUnHbaaenRo0d23nnnlJeX5/HHH88yyyyTs846K82bN8/gwYOz3377Ze21186ee+6Zdu3a5a233so///nPbLLJJvnLX/7yXV6SKuZ2H39Xnx2Srz4//eEPf0hFRUUOOeSQas/3dtttlyFDhqRFixZZbbXV8uijj+bee+9NmzZt5rnf+++/P0cddVR22223rLzyyvnyyy8zZMiQyvDR4qK2fWTnzp3TsmXLXHLJJWnWrFmaNGmSDTbYICussMJ3Uscaa6yRPn365Oijj055eXllSLo200T99Kc/zQEHHJDLLruscqrUsWPH5pprrsmOO+6YTTfdtEr7lVdeOYccckgef/zxLLXUUrnyyivz/vvvVxtJ8uu+z/fSb/MeAgAA/AAUAADAD8b2229fVFRUFNOmTZtrmwMPPLBo2LBh8dFHHxVFURSzZ88uOnToUCQpTj/99Bq3+eKLL4pzzjmnWH311Yvy8vKiVatWxTrrrFMMHDiwmDx5cmW7JMWRRx5Z4z7+9re/FV26dCnKy8uLrl27FldddVXRv3//4pv/7Jg2bVpx5JFHFq1bty6aNm1a7LjjjsUrr7xSJCnOPvvsKm3ff//94sgjjyw6dOhQNGzYsFh66aWLzTffvLjssstqdb2Koii+/PLLon379kWS4l//+le19Q8//HCx4YYbFo0bNy6WWWaZ4oQTTijuuuuuIknxwAMPVLY74IADio4dO1bZ9sMPPyx22WWXYokllihatWpVHH744cULL7xQJCmuuuqqKm3/97//Ffvvv3+x9NJLFw0bNiyWXXbZYrvttiuGDRtW2eb0008v1l9//aJly5ZF48aNi65duxZnnHFG8cUXX8z3PP/3v/8Vu+66a9GyZcuioqKiWH/99YsRI0ZUazev5/DrXnvttWL//fcvVlxxxaJx48ZFRUVFsfrqqxf9+/cvpk6dOt/ti6Io3nzzzaJv377FEkssUbRt27Y45phjin//+9+1urYdO3Ystt1221rX/+mnnxYnnXRSsdJKKxWNGjUq2rZtW2y88cbFueeeW3n93njjjSJJ8ac//anGemvzHF111VVFkuLxxx+vsu0DDzxQ7bzee++9Ytttty2aNWtWJCl69eo112v19drOO++8okOHDkV5eXnRo0eP4tlnn63S9p133il22mmnomXLlkWLFi2K3XbbrXj33XeLJEX//v2LoiiKGTNmFMcff3zx05/+tGjWrFnRpEmT4qc//Wlx8cUXVzv2008/Xey8885FmzZtivLy8qJjx47F7rvvXtx3333VzvuNN96oXNarV68q5zTnGtx00001nts3XxPzO+6CnENN3nzzzWL//fcv2rVrV5SXlxcrrrhiceSRRxYzZswoiqIoPv/88+I3v/lN0b59+6Jx48bFJptsUjz66KPVzuvSSy8tevbsWVln586di+OPP75K31gUteuvaruvuVlvvfWKJMWYMWMql73zzjtFkqJDhw7V2tfUB7/88stFz549i8aNGxdJigMOOKBK2w8//LBK+5qe+5qMGDGiWH/99YtmzZoVSyyxRLHhhhsWN954Y7V2d999d7HllltWvs5atmxZbLXVVlXut286/vjji3XXXbeYPXt2leVTp04t9t9//6Jly5ZF165di3//+9/zrPHrhg0bVmy11VZF69atiwYNGhTt27cv9thjj2LkyJFV2tWmb12Qe39u1/mb911R1L4PLYravY4X9Dm+8sori7XWWqvyc0GvXr2Ke+65p9q59+nTp2jRokVRUVFRdO7cuTjwwAOLJ554opiXmq7ZgtQ3t/u4KGr3Wpzbc/Z1r776apGkSFKMHj262vpPPvmkOOigg4q2bdsWTZs2Lfr06VO8/PLLRceOHavU8833h9dff704+OCDi86dOxcVFRVF69ati0033bS4995753nNFtQBBxxQNGnSpNryXr16Fauvvnq15d98361tH1kURXHbbbcVq622WtGgQYMq9/zcjlXT+35N5rzn//3vf6/8jLnWWmtVu//ndu8URVHMnDmzGDhwYLHCCisUDRs2LDp06FCcdNJJxeeff17j+d91113FT37yk8rPs9+8R2p6vy+Kb/carO1z9W3fQwAAgMVbWVHU4k9KAQAAFqFnnnkma621Vv7+979nn332KXU5AACUWFlZWY488shFOooTAADA4mLRje8LAABQg+nTp1dbNmjQoNSrVy89e/YsQUUAAAAAAFA6DUpdAAAAULf88Y9/zJNPPplNN900DRo0yJ133pk777wzhx12WDp06FDq8gAAAAAA4HslvAMAAHyvNt5449xzzz057bTTMnXq1Cy//PIZMGBATjnllFKXBgAAAAAA37uyoiiKUhcBAAAAAAAAAAB1Ub1SFwAAAAAAAAAAAHWV8A4AAAAAAAAAAJSI8A4AAAAAAAAAAJSI8A4AAAAAAAAAAJSI8A4AAN/agAEDUlZWVuWna9eupS4LAAAAAABgsdeg1AUAAPDjsPrqq+fee++tfNyggY+aAAAAAAAA8+MbFQAAvhMNGjTI0ksvXeoyAAAAAAAAflBMmwUAwHfi1VdfzTLLLJMVV1wx++yzT956661SlwQAAAAAALDYKyuKoih1EQAA/LDdeeedmTp1alZZZZVMmDAhAwcOzPjx4/PCCy+kWbNmNW4zY8aMzJgxo/Lx7Nmz8/HHH6dNmzYpKyv7vkoHAACgjiqKIp9++mmWWWaZ1Kvnb50BACgd4R0AAL5zkyZNSseOHXP++efnkEMOqbHNgAEDMnDgwO+5MgAAAKjq7bffznLLLVfqMgAAqMOEdwAAWCTWW2+9bLHFFjnrrLNqXP/NkXcmT56c5ZdfPm+//XaaN2/+fZUJAABAHTVlypR06NAhkyZNSosWLUpdDgAAdViDUhcAAMCPz9SpU/O///0v++2331zblJeXp7y8vNry5s2bC+8AAADwvTF1MwAApWYSVwAAvrV+/frlwQcfzLhx4/LII49kp512Sv369bPXXnuVujQAAAAAAIDFmpF3AAD41t55553stddemThxYtq1a5fu3bvnscceS7t27UpdGgAAAAAAwGJNeAcAgG/t+uuvL3UJAAAAAAAAP0imzQIAAAAAAAAAgBIR3gEAAAAAAAAAgBIxbRYAAMAPwMyZMzNr1qxSlwEA/MDUr18/DRs2LHUZAAAAzIPwDgAAwGJsypQp+eijjzJjxoxSlwIA/ECVl5enbdu2ad68ealLAQAAoAbCOwAAAIupKVOmZPz48WnatGnatm2bhg0bpqysrNRlAQA/EEVRZObMmZk8eXLGjx+fJAI8AAAAiyHhHQAAgMXURx99lKZNm2a55ZYT2gEAFkrjxo3TrFmzvPPOO/noo4+EdwAAABZD9UpdAAAAANXNnDkzM2bMSIsWLQR3AIBvpaysLC1atMiMGTMyc+bMUpcDAADANwjvAAAALIZmzZqVJGnYsGGJKwEAfgzmfKaY8xkDAACAxYfwDgAAwGLMqDsAwHfBZwoAAIDFl/AOAAAAAAAAAACUiPAOAAAAAAAAAACUiPAOAAAA1AGDBg1Ko0aNMm7cuFKX8oNx9dVXp6ysLFdfffX3crwePXpkgw02+F6ORe3NnDkzAwYMSJcuXVJeXp6ysrIMHz58gfczcuTIlJWVZcCAAVWW9+7de5FOZzS3487P8OHDU1ZWlkceeWTRFPYjpM8AAABgYQnvAAAAwI/cJ598ktNOOy0HH3xwOnXqVGXdl19+mSuvvDIbbbRR2rVrl2bNmmW11VbLCSeckPfee680BddRAwYMyNixY3P99deXuhS+5rzzzsvAgQOzzDLLpF+/funfv3+6du1a6rIWqZkzZ+aEE05Inz59svHGG1db//jjj+dnP/tZWrZsmSZNmmTDDTfMjTfeWIJK6zZ9BgAAwI9Hg1IXAAAAwMIpKzu31CXMVVH0K3UJfM0FF1yQjz/+OMcff3y1dXvssUduueWWrLTSStlzzz1TXl6exx57LH/605/y97//PU899VSWXnrpElRd92y++eZZe+21079//+yxxx6LdDSW78otr0wodQlztfMq7b+T/YwYMSJNmzbNPffck0aNGi30ftZff/385z//Sdu2bb+TuhalIUOG5NVXX80ll1xSbd0DDzyQPn36pKKiInvuuWeaNWuWm2++OXvssUfefvvt/OY3vylBxXXTD7HPAAAAoGZG3gEAAIAfsS+//DJXXHFFNtlkk3Tu3LnKurFjx+aWW27J+uuvn5deeikXXXRRzj333IwePTpHH310JkyYkMsuu6xElddN++67b/773//m/vvvL3Up/H/vvvtu2rRp862CO0myxBJLpGvXrj+I8M7gwYPToUOHbLrpplWWf/nllzn00ENTr169PPTQQ7nsssty3nnn5dlnn83KK6+ck08+OW+++WaJqq6b9BkAAAA/DsI7AAAALJZuvvnm9OrVK0suuWQqKiqyzDLLZIsttsjNN99c2WbkyJEpKyvLgAEDqm0/bty4lJWV5cADD6y27oMPPshvfvObrLLKKmncuHFat26dDTbYIOeeW300o2effTb77LNPlltuuZSXl6d9+/bZeuutc8cdd1Rre9ttt2XzzTdPq1atUlFRkTXWWCPnnntuZs2aVaXd7Nmzc8UVV2T99ddP69at07hx4yy33HLZfvvtM3LkyAW+DvPy73//OxMmTMhuu+1Wbd3rr7+eJNliiy3SsGHDKuu22267JMmHH35Yq+NMmDAhxxxzTLp06ZLGjRunZcuWWXXVVfOLX/wikydPrmz33//+NyeccELWXnvttGnTJhUVFVl55ZVz4oknZurUqdX227t375SVlWXGjBk5+eSTs/zyy6dx48ZZZ511cu+99yZJJk+enCOPPDLLLLNMKioqstFGG2Xs2LHV9tWpU6d06tQpkyZNyuGHH56ll146FRUVWWuttXLdddfV6jzneOONN/Lzn/88yy+/fOV9ceCBB9YYXHjqqaey6667VrZt165d1ltvvZxxxhnV2s55nq6++uoFqofv3oABA1JWVpY33ngjb775ZsrKylJWVlY59dwXX3yRiy66KH369EmHDh1SXl6eJZdcMjvvvHOefvrpavubV381N7XtU5Jk+vTpOfHEE9OhQ4fKtpdffvkCn/cLL7yQJ554Irvssku1kVzuv//+/O9//8vee++dNddcs3J5ixYtcvLJJ+eLL77INddcU6vj6DP0GQAAAPwf02YBAACw2Bk8eHCOOOKItG/fPjvttFPatGmT9957L2PHjs2tt96aXXbZZaH3/corr2TTTTfNhAkT0r179+y4446ZNm1aXnzxxZx55pnp1+//pvy6+eabs/fee6coimy//fZZZZVV8sEHH2TMmDH529/+lu23376y7UknnZSzzz47yy67bHbeeee0aNEio0aNyvHHH58xY8bkpptuqtL2j3/8Yzp37py99947zZo1y/jx4zN69Ojce++96d2793d2He67774kyYYbblht3eqrr54kuffeezNgwIAqAZ4RI0Yk+Wpalvn57LPPsskmm2TcuHHZaqutstNOO+WLL77IG2+8kSFDhqRfv35p0aJFkuSWW27J3/72t2y66abp3bt3Zs+encceeyznnHNOHnzwwTz00EPVgkTJV9N7Pf/88+nbt2+mT5+eoUOHZrvttsvDDz+cww47LF988UV22223fPjhh7nhhhuy9dZb54033qg87hxffPFFtthii0ydOjX77bdfpk2blhtvvDF77713Pvroo/zqV7+a7/mOGTMmffr0ybRp07LddtulS5cuGTduXIYOHZo777wzjz76aFZcccUkyTPPPJONN9449evXzw477JCOHTtm0qRJeemll3LZZZfllFNOqbLv5ZZbLh06dKh83iidOa/DQYMGJUmOPfbYJEnLli2TJB9//HGOPfbY9OjRIz/72c/SqlWrvP7667n99ttz55135qGHHsp666230MdfkD5l9uzZ6du3b+69995069Yte++9dyZOnJhf//rX1UbPmZ959RlzwoVbbbVVtXV9+vRJkjz44IPzPYY+Q58BAABAVcI7AAAALHauuOKKNGrUKM8880yWXHLJKusmTpz4rfa97777Vk4Hdeihh1ZZ984771T+/v777+eAAw5Iw4YNM2rUqKy11lpzbXvPPffk7LPPTp8+fXLzzTenSZMmSZKiKHLEEUfkkksuyc0331wZtrniiiuyzDLL5LnnnssSSyxRZb8ff/xx5e/fxXV4+OGHU69evSqjZMzRrVu3HHPMMbnwwguz2mqrZZtttkl5eXkeffTRPPnkkxk4cGB23HHH+R7jvvvuyxtvvJFjjz02F1xwQZV1U6dOrfLF+n777Zfjjjuu2hREp556avr3758bb7wx++yzT7VjTJw4Mc8991zlte3Tp0/22GOPbLHFFtlyyy3zj3/8Iw0afPXfHGuuuWZ++9vf5m9/+1uOO+64KvuZMGFCunTpkkceeaSyhpNPPjlrrbVWjj/++Oy8885Zdtll53quM2fOzJ577pnZs2dn7NixVe6L0aNHp3fv3jnmmGMqR2YaMmRIZsyYkeHDh2eHHXaodk41WXfddXPrrbfmjTfeyAorrDDXWli0evfund69e1eOaPLNEXNatWqVt956q9r98uKLL2bDDTfMySefnHvuuWehjr2gfcq1116be++9N1tvvXVGjBiR+vXrJ0mOOeaYrLvuugt07IcffjhJss4661Rb9+qrryZJunTpUm3d0ksvnaZNm1a2mRd9xlf0GQAAAMxh2iwAAAAWSw0bNqxxNIU2bdos9D7Hjh2bJ554Ij179qwW3Em+GsFgjmuuuSbTpk3Lb37zm2rBnW+2/ctf/pIkueyyyyq/KE6SsrKynH322SkrK6s2xUqjRo0qv2D/utatW1d5/G2vwzvvvJOWLVumvLy8xvWDBg3KoEGD8uabb+aiiy7Kueeem4cffjhbbrlldt5551odY47GjRtXW9a0adMqx1522WWrfQmfJEcddVSSVE5r801nnHFGlWu76667pmHDhpk0aVLOPffcyi/hk2SvvfZK8tWUZzU588wzq9Sw3HLL5ZhjjsmMGTNy/fXXz+sUM2LEiIwbNy7HH398tfuie/fu2WGHHfKvf/0rU6ZMqbKupmszt+dwqaWWSlI1IMbip7y8vMbQxuqrr55NN900Dz30UGbOnLlQ+17QPuXaa69N8tXr5Ov9Srdu3bLffvst0LHn3Hdz7sOvmzOd1TdHp5mjefPmVaa8mh99hj4DAACArxh5BwAAgMXOnnvumRNOOCFrrLFG9t5772y66abp3r17mjdv/q32O3bs2CQ1T/nybdo+9thjadKkSa688soa1zdu3Dgvv/xy5eM999wzF198cdZYY43sueee2XTTTbPRRhtV+7L2u7gOEydOrBI0+rrZs2fnF7/4Ra677rpcdNFF2WGHHbLEEkvk4YcfztFHH50NN9wwDzzwwHyn/unZs2fat2+fs88+O88++2y222679OrVK6uuumrKysqqtC2KIldddVWuvvrqvPDCC5k8eXJmz55duf7dd9+t8RjfHDmoXr16WXLJJfPZZ59l+eWXr7Kuffv2c91XgwYNstFGG1Vb3qNHjyTJ008/Pc9zfeyxx5J8Nf3aN0diSZL33nsvs2fPzn//+9+su+662X333TNo0KDstNNO2WOPPbLlllumZ8+e8xypY06A66OPPppnLZTeM888kz/+8Y8ZPXp03nvvvWphnY8++qjyflwQC9qnPPvss2nSpEnWXnvtam179OiRv/3tb7U+9sSJE1O/fv00a9ZsgeuuLX3G/9FnAAAAkAjvAAAAsBjq169f2rRpk8GDB+e8886rHCVh2223zQUXXLDQ04LMGRFiXl+CLkzbjz/+OF9++WUGDhw41zbTpk2r/P3CCy/MCiuskKuuuiqnn356Tj/99FRUVGT33XfPeeedl7Zt2yb5bq5D48aN8/nnn9e47sorr8zll1+eCy+8MIcffnjl8m222SbDhg3LmmuuWaupf1q0aJHHHnssf/jDH3LHHXfkX//6V5KkQ4cOOfHEE3PEEUdUtj366KPzl7/8JR06dEjfvn3Tvn37ylE2Bg4cmBkzZtR4jJoCSw0aNJjr8iQ1jnrStm3b1KtXfSDiOSNXzG/UkDnTmg0dOnSe7eY83xtssEFGjhyZM888M//4xz9y1VVXJUnWW2+9nHPOOdl0002rbTt9+vQkqTalGouXRx55JJtttlmSr0J+Xbp0SdOmTVNWVpbhw4fn2Wefnev9PD8L2qdMnjw5HTp0qLFdTSPozEvjxo0za9aszJw5s9qoX3NG3Jnb62TKlClp1arVfI+hz6hOnwEAAFC3Ce8AAACw2CkrK8vBBx+cgw8+OBMnTsyoUaNy3XXX5cYbb8yrr76a5557LvXr16/8QvXLL7+sto+avlBt2bJlkmT8+PHzreHrbTt16jTPts2bN09ZWVmtRz1o0KBB+vXrl379+uXdd9/Ngw8+mKuuuirXXntt3nvvvdx1111Jan8d5qVdu3ZznUrlzjvvTJIavwz+6U9/mlatWs13VIk5ll9++Vx99dWZPXt2nnvuudx9993585//nCOPPDKtWrXKXnvtlQ8++CB//etf85Of/CSPPvpolS+a33vvvXkGFb4rH330UWbPnl3ty/j3338/ydynA5pjzhf/d9xxR7bbbrtaHbNHjx658847M3369IwZMyZ33HFHLr744my77bZ54YUXsuKKK1ZpP+fL/nbt2tVq/5TGGWeckRkzZmTUqFHp3r17lXWPPfbYXKdgqo0F7VNatGiRDz/8sMZ1c+7t2ppz33388cfVgj9dunRJkrz66qtZZ511qqx77733MnXq1Ky//vq1Oo4+Y+70GQAAAHVP9T8bAQAAgMVImzZtsuOOO+aGG27IZpttlpdeeimvvfZaklSO8FBTGKem0MmcL5Xvvvvu+R53QdpusMEGmThxYl599dX5tv2mZZZZJnvttVf+/e9/Z6WVVsq9995bOYrC183rOsxLt27d8vnnn+ett96qtu6LL75Ikhq/9J8xY0Y+/fTTyhEuaqtevXpZc801c8IJJ+S6665Lktx+++1Jktdffz1FUWSLLbaoNkLEqFGjFug4C+vLL7/Mo48+Wm35nOOvtdZa89x+gw02SJIa9zE/jRs3Tu/evXPeeefl5JNPzvTp02sc1eiVV15Jw4YN07Vr1wU+Bt+f//3vf2ndunW14M5nn32Wp5566lvte0H7lJ/+9KeZNm1ajcdd0NdWt27dknx1H35Tr169ktTcL84JHc5pU1v6jLnTZwAAANQdwjsAAAAsdkaOHJmiKKosmzlzZuXoAhUVFUmSVVZZJc2aNcvtt99euS75akSE008/vdp+11tvvay33np56KGHcvnll1db//UQ0AEHHJCmTZvmvPPOyzPPPDPPtkcffXSSVI6Q803vvfde/vOf/yT5KhTzyCOPVGszbdq0TJ06NQ0bNqwc4aG212Fe5nyRPmbMmGrrNtlkkyTJmWeeWW3qmQEDBuTLL7+scVSeb3rxxRdrHN1jzrI5dXbs2DHJV9MNzZ49u7LdO++8k5NOOmm+x/munHzyyZXBpTnHv/DCC1NeXp4999xzntvusMMOWX755XP++efnoYceqrZ+5syZGT16dOXjRx99tMZpy755beb44osv8vTTT2fdddc1Bc5irmPHjvnkk0/y4osvVi6bNWtW+vXrN9dRcGprQfqUJNlvv/2SJKecckpmzZpVufz555/PkCFDFujY8+ozNt9886y44or5xz/+UaVfnDx5cs4888w0atQo+++//3yPoc/4P/oMAAAAEtNmAQAAsBjacccd07x582y44Ybp2LFjZs6cmXvuuScvvfRSdt1118ovdBs1apRf/epXOfPMM7P22mtnhx12yKeffpo77rgjvXr1yv/+979q+x46dGh69+6dww47LEOGDMlGG22Uzz//PC+++GKefvrpyi/Kl1xyyVx77bXZc889s/7666dv375ZZZVV8tFHH2XMmDHp1KlThg8fniTZeuut8/vf/z6nnXZaVlpppWy99dbp2LFjJk6cmNdeey2jRo3K6aefnlVXXTXTp0/PJptskpVXXjnrrLNOll9++UydOjUjRozIe++9l379+lWOdlPb6zAvO+ywQ4477rjcc8892W233aqsO+KII3LNNdfkvvvuS9euXbP11luncePGefjhhzN27Ni0a9cup5566nyPcc899+T444+vPK82bdrk9ddfz+23356KiooceeSRSZL27dtnl112yc0335x11103m2++ed5///2MGDEim2++eY3P13etffv2mTZtWn7yk59k++23z7Rp03LjjTdm4sSJ+fOf/5xll112ntuXl5dn2LBh2WabbdKrV69sttlm6datW8rKyvLmm29m1KhRadOmTV5++eUkyTnnnJMHHnggPXv2zAorrJCKioo89dRTue+++7Liiitmp512qrL/UaNGZcaMGdlxxx0X1SXgO/KrX/0qd999d7p3757dd989FRUVGTlyZMaPH5/evXtn5MiRC73vBelTkq/Chv/4xz/y73//O2uttVa22WabfPzxx7nuuuuy1VZbZcSIEbU+9uabb55mzZpVvq6/rkGDBrniiivSp0+f9OzZM3vuuWeaNWuWm2++OW+++WbOPffc+U4zmOgz9BkAAAB8k/AOAAAAi52zzjor//73vzN27NjccccdadKkSTp37pzBgwfnkEMOqdL2tNNOS6NGjfK3v/0tl1xySTp16pTf//732X777XPzzTdX23eXLl3y1FNP5ayzzsodd9yRQYMGpWnTpunSpUt+97vfVWm70047ZcyYMTnrrLPy4IMP5vbbb0/btm2z5ppr5tBDD63S9tRTT03Pnj3z5z//Offdd18mTZqUNm3aZIUVVsiAAQOyzz77JEmaNGmSc845J/fdd19GjRqVDz74IK1atcoqq6ySs846q8ooDgtyHeamU6dO6dOnT4YNG5aLLrqoyjRYzZs3z2OPPZZzzjknt912W66++urMmjUryy23XH7xi1/klFNOyXLLLTffY/Tp0yfjxo3LQw89lFtuuSVTp07Nsssumz322CMnnHBCVltttcq2V199dTp16pSbb745F110UZZffvkcd9xx+e1vf5thw4bV6py+jUaNGuWee+7JiSeemCFDhmTSpEnp2rVrLrroouy111612sd6662XZ599Nn/605/yr3/9Kw8//HDKy8uz7LLLZscdd6yyn1/+8pdp0aJFxowZkwcffDBFUWT55ZfPySefnF//+tdp3rx5lX3//e9/T6NGjXLQQQd9p+fNd2+77bbLsGHDcuaZZ+bvf/97llhiiWy22Wa59dZbaxV6m5/a9inJV1NP3XbbbRk4cGCGDh2aCy+8MJ07d84FF1yQLl26LFB4p2nTptl3331z2WWXZcKECWnfvn2V9ZtuumlGjx6d/v3754YbbsjMmTPTrVu3nHPOOdljjz1qdQx9hj4DAACAqsqKb46/DQAAJTBlypS0aNEikydPrvalBNRFn3/+ed54443Kv7qHb+O+++7LFltskb///e9VvvCva+aMCDJu3LiS1jE3n3zySTp27Jhdd901V155ZanLoQ575ZVXssYaa2TAgAE55ZRTSl1O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AAAAAMAPnZF3AABIkvTu3TtrrbVWzj///O9kf7Nnz86MGTO+k30BAAAAAAD8WBl5BwCASiuuuGLuvPPOBd7upJNOyjbbbJPll18+n376af7xj39k5MiRueuuuxZBlQAAAAAAAD8ewjsAAFR69dVXUxTFAm/3wQcfZP/998+ECRPSokWL/OQnP8ldd92VLbfcchFUCQAAAAAA8OMhvAMAQL788succ845eeaZZ9K9e/cF3v5vf/vbIqgKAAAAAADgx094BwCgDthss83muu7TTz/N66+/nkmTJqVevXo5+eSTv8fKAAAAAAAA6jbhHQCAOmDkyJHzbdOlS5ecffbZ2XrrrRd9QQAAAAAAACQR3gEAqBMeeOCBua5r1KhRll122Sy//PLfY0UAAAAAAAAkwjsAAHVCr169Sl0CAAAAAAAANahX6gIAAAAAAAAAAKCuEt4BAAAAAAAAAIASMW0WAMCP0GabbbbQ25aVleW+++77DqsBAAAAAABgboR3AAB+hEaOHLnQ25aVlX13hQAAAAAAADBPwjsAAD9CDzzwQKlLAAAAAAAAoBaEdwAAfoR69epV6hIAAAAAAACohXqlLgAAAAAAAAAAAOoq4R0AAAAAAAAAACgR02YBANQhEyZMyG233ZZXXnklU6ZMSVEU1dqUlZXlb3/7WwmqAwAAAAAAqHuEdwAA6oiLLrooxx9/fGbOnFm5bE54p6ysrPKx8A4AAAAAAMD3x7RZAAB1wH333ZdjjjkmFRUVOfHEE7PRRhslSS699NL85je/SadOnZIkxx57bK688soSVgoAAAAAAFC3CO8AANQBF154YcrKynLXXXfljDPOSJcuXZIkhx56aP70pz/lpZdeygEHHJArr7wyPXr0KHG1AAAAAAAAdYfwDgBAHTB27Nisvfba2WCDDWpcX15ensGDB6eioiKnnnrq91wdAAAAAABA3SW8AwBQB3zyySfp3Llz5eOGDRsmSaZPn165rLy8PD169Mh99933vdcHAAAAAABQVwnvAADUAa1bt860adMqH7dq1SpJ8tZbb1VpN2vWrEycOPF7rQ0AAAAAAKAuE94BAKgDll9++bz99tuVj9dYY40URZERI0ZULps6dWpGjRqV5ZZbrhQlAgAAAAAA1EkNSl0AAACLXq9evXLBBRfk/fffz1JLLZVtt902TZo0ycknn5z33nsvyy+/fK655pp8/PHH2XPPPUtdLgAAAAAAQJ0hvAMAUAfstttuefrpp/PMM8+kT58+ad26dc4///z84he/yPnnn58kKYoinTp1ysCBA0tcLQAAAAAAQN0hvAMA8CM0bNiw7LjjjmnQ4KuPe+utt17uueeeKm0OPfTQrLPOOrnpppvy8ccfZ9VVV81BBx2UFi1alKJkAAAAAACAOkl4BwDgR2j33XdPu3btsu++++aggw7KGmusUWO7tddeO2uvvfb3XB0AAAAAAABz1Ct1AQAAfPfatGmTDz/8MIMGDcpPf/rTbLjhhrn88svz6aeflro0AAAAAAAAvkZ4BwDgR2jChAkZNmxYtt5669SrVy9jx47NL37xi7Rv3z4HHnhgHnrooVKXCAAAAAAAQIR3AAB+lBo0aJCdd945//znP/P222/nzDPPTJcuXfLZZ5/l2muvzaabbpqVV14555xzTiZMmFDqcgEAAAAAAOos4R0AgB+5pZdeOieeeGJefvnljB49OgcddFCaNGmS1157LSeffHI6duyYvn375rbbbsusWbNKXS4APzpTkrzzjZ/xX1s/vob17/z/7QAAAADgx69BqQsAAOD7s/HGG2fjjTfORRddlBtuuCFXXXVVRo8enREjRuSf//xn2rVrl/333z9//OMfS10qAD8ajyW5Zx7rL57L8i0XQS0wx5RUD4jN/Nrv45M0rGG75ousIgAAAKDuEt4BAKiDllhiiRx00EE56KCD8r///S9XXnllLrvssnzwwQc577zzhHcA+A5tmGS1hdhOSIJFSagMAAAAWHwI7wAA1GEzZszI2LFjM3bs2HzyySelLgeAH6XmEcRh8SNUBgAAACw+hHcAAOqgJ554IldeeWWuv/76TJ48OUVRpH79+vnZz36WQw45pNTlAQAsYkJlAAAAwOJDeAcAoI746KOP8ve//z1XXXVVXnjhhSRJURRZccUVc/DBB+fAAw/MMsssU+IqAQAAAAAA6hbhHQCAH7HZs2fnzjvvzJVXXpl//vOfmTlzZoqiSEVFRXbeeecccsgh2XTTTUtdJgAAAAAAQJ0lvAMA8CP03//+N1deeWWGDBmS9957L0VRJEnWXHPNHHLIIdl3333TokWLElcJAAAAAACA8A4AwI/QqquumuSrabFatmyZvffeO4ccckjWWmutElcGAAAAAADA1wnvAAD8CBVFkd69e+eQQw7JLrvskoqKilKXBAAAAAAAQA2EdwAAfoRee+21rLjiiqUuAwAAAAAAgPmoV+oCAAD47gnuAAAAAAAA/DAI7wAAAAAAAAAAQIkI7wAAAAAAAAAAQIkI7wAAAAAAAAAAQIkI7wAAAAAAAAAAQIkI7wAAAAAAAAAAQIkI7wAAAAAAAAAAQIk0KHUBAAB8/1577bV8+OGHadOmTVZeeeVSlwMAAAAAAFBnGXkHAKCOmDVrVk4//fQsvfTSWWWVVdK9e/ecffbZleuHDh2ajTfeOC+++GIJqwQAAAAAAKhbhHcAAOqAWbNmZbvttkv//v3zySefZNVVV01RFFXabLLJJnnsscdyyy23lKhKAAAAAACAukd4BwCgDrjkkkty1113ZdNNN80bb7yRF154oVqbTp06pXPnzrn77rtLUCEAAAAAAEDdJLwDAFAHXHPNNWndunVuuummLLPMMnNtt+qqq+att976HisDAAAAAACo24R3AADqgJdffjnrr79+WrVqNc92LVq0yAcffPA9VQUAAAAAAIDwDgBAHTBr1qyUl5fPt92ECRNq1Q4AAAAAAIDvhvAOAEAd0LFjxzz33HPzbDNz5sy88MIL6dKly/dUFQAAAAAAAMI7AAB1wNZbb51x48blsssum2ubiy66KB9++GG23Xbb77EyAAAAAACAuq1BqQsAAGDRO/7443P11VfniCOOyEsvvZTdd989STJt2rQ89dRTufHGG3P++eenbdu2Oeqoo0pcLQAAAAAAQN1h5B0AgDqgffv2GT58eFq2bJk///nP6dGjR8rKyjJs2LCst956+eMf/5imTZvm5ptvTtu2bUtdLgAAAAAAQJ0hvAMAUEf07NkzL774Yk444YSsvvrqady4ccrLy7PSSivl6KOPzvPPP5/u3buXukwAAAAAAIA6xbRZAAB1yFJLLZWzzz47Z599dqlLAQAAAAAAIEbeAQAAAAAAAACAkhHeAQAAAAAAAACAEjFtFgBAHbDZZpvVql2jRo3Stm3brLvuutlrr72y1FJLLeLKAAAAAAAA6jbhHQCAOmDkyJFJkrKysiRJURTV2pSVlVUuv+6663LKKadk8ODB2X///b+3OgEAAAAAAOoa4R0AgDrggQceyIgRI3LeeedlvfXWy957751OnTqlrKws48aNyz/+8Y+MHTs2xx13XNZcc83cf//9ueaaa/Lzn/88Xbt2zfrrr1/qUwAAAAAAAPhREt4BAKgDGjVqlAsvvDDnn39+jj322Grrjz766Fx44YU5/vjjM3LkyOy7777ZaKONcvjhh+fCCy/M0KFDv/+iAQAAAAAA6oB6pS4AAIBF77TTTkvXrl1rDO7Mccwxx6Rr1645/fTTkyQ///nP06lTp4wePfp7qhIAAAAAAKDuEd4BAKgDxo4dm27dus23Xbdu3TJmzJgkSVlZWVZbbbV88MEHi7o8AAAAAACAOkt4BwCgDpg+fXomTJgw33YTJkzI559/Xvm4SZMmadDATKsAAAAAAACLivAOAEAdsOqqq2bUqFGVo+rUZMyYMRk1alRWW221ymXjx49P27Ztv48SAQAAAAAA6iThHQCAOuCII47IrFmzstVWW+X3v/99/vOf/2T69OmZPn16Xn755fzhD39Inz59Mnv27Pzyl79Mknz22Wd5+umns84665S4egAAAAAAgB8vcyAAANQBBx98cJ544olccsklOfPMM3PmmWdWa1MURQ4//PAcfPDBSZJx48Zl9913z5577vl9lwsAAAAAAFBnCO8AANQRF198cbbeeutceOGFefTRR/P5558nScrLy7PRRhvl6KOPzo477ljZfrXVVstVV11VomoBAKDumDBhQiZMmLDA27Vv3z7t27dfBBUBAADwfRLeAQCoQ/r27Zu+fftm1qxZ+eijj5Ikbdq0SYMGPhYCAECpXHrppRk4cOACb9e/f/8MGDDguy8IAACA75VvaQAA6qD69etnqaWWKnUZAABAksMPPzx9+/atsmz69Onp3r17kmT06NFp3Lhxte2MugMAAPDjILwDAAAAAFBCNU1/NW3atMrf11xzzTRp0uT7LgsAAIDvifAOAEAdMmHChNx222155ZVXMmXKlBRFUa1NWVlZ/va3v5WgOgAAAAAAgLpHeAcAoI646KKLcvzxx2fmzJmVy+aEd8rKyiofC+8AAAAAAAB8f+qVugAAABa9++67L8ccc0wqKipy4oknZqONNkqSXHrppfnNb36TTp06JUmOPfbYXHnllSWsFAAAAAAAoG4R3gEAqAMuvPDClJWV5a677soZZ5yRLl26JEkOPfTQ/OlPf8pLL72UAw44IFdeeWV69OhR4moBAAAAAADqDuEdAIA6YOzYsVl77bWzwQYb1Li+vLw8gwcPTkVFRU499dTvuToAAAAAAIC6S3gHAKAO+OSTT9K5c+fKxw0bNkySTJ8+vXJZeXl5evTokfvuu+97rw8AAAAAAKCuEt4BAKgDWrdunWnTplU+btWqVZLkrbfeqtJu1qxZmThx4vdaGwAAAAAAQF0mvAMAUAcsv/zyefvttysfr7HGGimKIiNGjKhcNnXq1IwaNSrLLbdcKUoEAAAAAACokxqUugAAABa9Xr165YILLsj777+fpZZaKttuu22aNPl/7N15vJZz/j/w12nXLkuWKIpJGMY2hDC2sY8xyhj7ngpJ2pSsSdbIILuyNtbsIjExdrIvY0+2lJS2c+7fH37n/p6UGYO6W57Px6NHdV/XffW+ruv+fM7p+rzO51MvvXv3zoQJE7Lqqqvm2muvzcSJE7PPPvuUulwAAAAAAIAlhvAOAMASYO+9984LL7yQF198MTvuuGOaNGmS8847L0cddVTOO++8JEmhUEiLFi1yyimnlLhaAAAAAACAJYfwDgDAEmDjjTfOQw89NMdrhx9+eDbccMPceuutmThxYtZaa60cfPDBadSoUYmqBAAAAAAAWPII7wAALME22GCDbLDBBqUuAwAAAAAAYIlVrdQFAAAw/62++ur54x//WOoyAAAAAAAA+AHhHQCAJcBnn32WJk2alLoMAAAAAAAAfkB4BwBgCdC8efN88803pS4DAAAAAACAHxDeAQBYAvzlL3/JmDFj8sUXX5S6FAAAAAAAAKoQ3gEAWAL06tUra621VnbYYYeMHTu21OUAAAAAAADw/9UodQEAAMx/u+yyS6pXr56XXnopW265ZZZffvm0aNEiSy211Fz7lpWVZdSoUSWoEgAAAAAAYMkjvAMAsAQYPXp08c+FQiGfffZZPvvss3nuW1ZWtoCqAgAAAAAAQHgHAGAJ8Oijj5a6BAAAAAAAAOZBeAcAYAmw1VZblboEAAAAAAAA5qFaqQsAAAAAAAAAAIAllZl3AACWIIVCIffdd1/Gjh2bL774Ir///e9zyCGHJEm++OKLfP3112nZsmWqV69e4koBAAAAAACWDMI7AABLiJdeeikdOnTI22+/nUKhkLKyssyaNasY3nnooYey//7754477shuu+1W4moBAAAAAACWDJbNAgBYAnz88cfZbrvt8tZbb2WnnXbK2WefnUKhMMc+f/rTn1KzZs3ceeedJaoSAAAAAABgySO8AwCwBDjzzDPz1Vdf5YILLsjIkSNzwgknzLVP3bp1s9566+WZZ54pQYUAAAAAAABLJuEdAIAlwP3335/WrVvnmGOO+Y/7tWjRIp9++ukCqgoAAAAAAIAapS4AAID5b/z48dljjz3+635lZWX55ptvFkBFAABAkpSVnfMjW2YW/1S//oVJav3oMQqFuWfWBAAAYNFh5h0AgCVAvXr18sUXX/zX/d577700adJkAVQEAAAAAABAIrwDALBEWHfddfPcc8/lyy+//NF9Pvjgg7z00kvZcMMNF2BlAAAAAAAASzbhHQCAJcB+++2XKVOm5LDDDsu0adPm2j5z5swcffTRmTVrVvbbb78SVAgAAAAAALBkqlHqAgAAmP8OPvjgDB8+PHfddVdat26dP/7xj0mSl156Kcccc0zuuuuufPjhh9luu+3SoUOHElcLAAAAAACw5DDzDgDAEqB69eq5++6789e//jWffPJJrrjiiiTJCy+8kIsvvjgffvhh9tprr9x2220lrhQAAAAAAGDJYuYdAIAlRP369TN8+PD07ds39957b/7973+noqIiq6yySnbaaaesv/76pS4RAAAAAABgiSO8AwCwhGndunVat25d6jIAAAAAAACIZbMAAJYId999dyoqKkpdBgAAAAAAAD8gvAMAsATYY489ssoqq6RHjx55/fXXS10OAAAAAAAA/5/wDgDAEmCDDTbIp59+mkGDBmWdddZJ27ZtM3To0HzzzTelLg0AAAAAAGCJJrwDALAEePbZZ/Pyyy/nuOOOy7LLLpunnnoqRx11VFZcccUccMABeeSRR37R8QcMGJCNN944DRo0yPLLL58//elPefPNN3+l6gEAAAAAABZfwjsAAEuIddZZJ+edd14++eST3Hbbbdl1110za9asDBs2LNtvv31WW221nHrqqfnggw/+52M/9thj6dSpU5566qk89NBDmTVrVnbYYYdMnTp1PpwJAAAAAADA4kN4BwBgCVOjRo386U9/yp133plPPvkk55xzTtq0aZMPPvggp5xySlq1avU/H/P+++/PQQcdlLXXXjvrrbderrnmmnz44Yd57rnn5sMZAAAAAAAALD6EdwAAlmDLLbdcjj/++Dz99NM59thjUygUUlFR8YuPO3ny5CRJkyZNfnSfGTNm5JtvvpnjFwAAAAAAwJJG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1lunUCiUugwAAAAAAIBFSrVSFwAAAAAAAAAAAEsq4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAAAAAAACgR4R0AAAAA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4us/rFmABxZ+//jHP1JRUZFJkyalfv36Sb5fHuiHqlevXgwlCPD8MoVCIbNmzUqnTp3y3XffZdVVV80mm2ySZN7XXoBn/ps2bVpOPfXUVFRUpF69etl4442TJLNmzZpr3/kd4BHeAQAAAABgvqhXr1422WSTTJ06NYMGDUry/YDuvAYnBHiAJVGLFi2y2WabZerUqRk8eHBmzZo1x1JZVS0MAZ4DDzww1113XWbNmpWDDjooN910U5I5wzqVBHhg4XbxxRfnmGOOyZtvvpk//elPeeONN4rLA/1Q1VCCAM/PV1ZWlpo1a+bxxx/PGmuskeuuuy49evTIzJkz51imrCoBnvmrbt26ueCCC7LDDjvkrrvuymGHHZYkqVmz5jzvx/wM8AjvAAAAAADwqysUCmnZsmWuvfba7Lzzzrn55pvTvn37JP83CPFDAjzAkqS8vDyNGjXKvffem+233z533HFH9t5770yfPv1H+7tSBngqByP322+/XHrppSkvL8/hhx+em2++OclPD/Ast9xy6datWy644IL5Wi/w4yr7kQsuuCCdOnXKuHHj0r59+7z11ls/Gj6oVq3aPJfQ+tvf/pbHH398gda/KJs9e3ZWXHHFPPbYY2nRokUGDRqUk046KeXl5T/a9wvwzD+FQiHrrLNOLrzwwvzhD3/IVVddlUMOOSRJfvR+zK8AT1lhXnPvAQAAAADAL1QoFFJWVpb33nsvXbp0yb333pu99967ONA7e/bs1KhRY673VVRUpFq1apk0aVKOPfbYXH/99Wnbtm1Gjx6dGjVqFI8LsKirHKydPHly2rdvn4ceeih77LFHbrzxxtSpU6e4/cfelyQHHHBAhg0bls022yw33XRTVllllflWb2X/nCTXX399jjzyyFSvXj1Dhw7NPvvskyTz7KOrvjZmzJjsu+++GT9+fC6++OIcffTR861e4MdV7UeOOeaYXHzxxVlnnXVy66235je/+c0c7b2qqq+fe+656d69exo3bpyRI0embdu2C/QcFlWV3wNPmDAhbdu2zfvvv58TTjghAwYMKAZG5tX3V75v+vTp2WGHHfLEE09k7733zqWXXpqll166BGeyeKj8GvXGG2+kc+fOeeSRR3LQQQflqquuSpIfvR+VbWH8+PHp3r17brzxxmy55ZZ57LHHflYdZt4BAAAAAGC+qJxxYbXVVstFF12UnXfeObfeems6dOiQ5KfNwHPppZfmyCOPzNixY/PnP/9ZcAdYrFQO0jZq1Ci33HJLtt9++9x5553561//+pNn4LnuuutyxBFH5Mknn8yRRx6Zb775Zr7VW3U2gf333z+XXXZZcQae/7aEVuX72rVrl9/97ndZZpllMmHChHz77bfzrV7gx1XtRwYPHpzOnTvnlVdeyd57750333zzP87AM2vWrCRJt27dstFGG6WsrCxPPPFEZs6cuUDPYVFV+T3wCiuskLFjx6ZFixY555xz0qtXr/86A8+MGTNSp06djBkzJo0aNcqzzz6bhx9+eK5+l5+u8utW69atc/HFF+cPf/hDrrnmmp80A8/MmTOz0kor5dprr82qq66axx9/PNdcc83PqkN4BwAAAACA+aZywPbnBHiSZKmllsrMmTPTpEmTtGjRIl9++eUCrR9gfvslAZ6qll566fz2t7/N9OnT52u9PzfAU9mvd+nSJffcc086duyYI444IvXr15+v9QI/7ucGeGrWrJkkOfTQQ/Pss8+mU6dO+etf/5patWot0PoXZT83wFO7du0kyRFHHJHJkydnn332Sdu2bYXbf6GqAZ4hQ4b85ABP5Wf+8MMPz4QJE9KnT59ss802P68Gy2YBAAAAADC//dwltM4+++z07Nkzffr0SceOHbPSSist6NIBFojK5Tf+1yW0/v73v6dTp07p2bNnOnfuPN/6yaozn/2wz/5fltAaMWJExo0blyOPPFKfDiVStW3+sJ3+1CW0Zs+enX79+mXGjBk54YQTsuKKKy7Qc1hU/fB6V17b/2UJrffeey8HHnhgNtxww5x44omu/S/wY7N6/i9LaE2bNi2bb755tthii/Tp0ycrrLDCz6pFeAcAAAAAgF9d1QfhkydPTqNGjYrb/pcAz7Rp0zJs2LDssssuWXnllRfcCQDMZ1X7ySlTpqRBgwbFbf9LgOezzz7LqFGjsvXWWy+Q4M4TTzyR8ePHZ9ttt80yyyxT3GdeAZ55DfgnyYwZM4qzRwALVtX2/Oyzz+a7777L73//+9SoUaPYXucV4Jk5c+ZcM+uUl5fnu+++M4PWT1T12r/99ttZeumls+yyyxb79h8L8MyaNas421Hy/XUfP3586tWrlyZNmpTqdBZ5Ve/Hq6++moqKiqy77rrF7W+++WY6deo0V4BnXm1h+vTp+fbbb7Psssv+7HqEdwAAAAAA+FVVfRD+yCOP5IEHHshuu+2WLbbYojiQ+2MBHoAlQdV+8qGHHsqoUaOy1157ZeONNy4GGX8swPPfjjc/a33kkUfSpUuXTJs2LU899VSaNm06x/aqAZ5LL700f/vb35IkY8aMyZZbbmlZFyixebXnZZddNnfeeWcaN248R0CwaoDnxhtvzNprr50k+fe//53VV1+9ZOewqKp67R9++OEcf/zx2XHHHTNgwIDiElo1atSYK8Bz6qmn/mjfz8/3w/tx0kknZc0118zf//731K1bN8n3S2n92Aw8yffh2aZNm/5qNc0ddQUAAAAAgJ/ph4NC3bp1y0UXXVR8sF35E92rrbZaLrroouy888659dZbs/feexeP8fe//z3jxo1b8MUDLAA/7Cd79OiRs88+O0svvXSSpEaNGikUCmnUqFFuueWWbL/99rnzzjvToUOHVP5M/nnnnZcPPvigeMwFEdwZNWpUevbsmQ8//DDDhw9P06ZNM3PmzMyYMaO4//7775/LLrss5eXl6dixY+6555506tQpW2+9dZ555pn5UiPw0/yw7+nVq1fee++9DBgwII0bN87s2bPn6EsGDx6czp0755VXXsm+++6bzz//PEceeWS22WabfPjhh6U6jUXSD699375989prr+XPf/5zcdbJGjVqpKKiIiussELGjh2bFi1a5Jxzzskpp5ySJDnggAPSvn37kp3D4uSH9+Okk07KM888k86dO6devXpzbG/dunUuvvji/OEPf8g111yTww8/PEly+OGHp2vXrvnyyy9/tbrmXkAYAAAAAAB+hh8+CO/Zs2feeeedPPLII1ljjTUybdq0TJo0qbisS2WAp1Ao5B//+EcOPPDANGjQIJdcckmuv/76tGnTZq7lYQAWZfPqJ99+++08+eSTadWqVaZNm5YpU6YUA4+VAZ727dvn7rvvzl//+tc0aNAgV155ZRo3bpyDDjponstSzY9ae/XqlVdeeSWjR4/OJptskkmTJuW2227LzJkzi3VVq1Yt+++/f5KkY8eO2W233VK/fv0cf/zxWWWVVeZLncB/N6++54ft+eGHH069evWy3XbbpVq1aqlevXoGDx6csrKyXHTRRVl99dUze/bsHHXUUVlqqaVKfEaLjh+79mPHji1e+3HjxqVVq1ZZccUVM3v27GKAZ/PNN8/AgQNz7733Zty4cenUqVMmTZqUxo0bl/akFmE/dj+efPLJbLLJJpk8eXL+9a9/pXHjxtlkk01SKBSKAZ7OnTvnyiuvzKhRo/L++++na9eucyxn9kuZeQcAAAAAgF/sxx6EP/zww9l0000zadKkXHXVVenevXteeumlJElFRUVWW221DBkyJLvsskuuv/76DBs2LD169Mg222wjuAMsVuY1i01lP/n73/8+kyZNyuWXX55u3brltddeS5KUl5cXAzw77rhjbrnlltx+++3p1atXdtxxxwUW3Kms9dFHHy0Obg4fPjyHHXZYHnjggTRq1CjVqlUrzgy0//77Z/DgwWnRokU6d+6cbt26ZcUVV5wvtQL/2U9pz8OGDUv79u1z1113pWbNmqlevXrKy8uTJBdeeGGOOOKI1K1bNyeccEJ69OiR5ZZbrpSntMj4Kdf+2muvzdZbb50RI0YkSXEJrRVWWCH//Oc/s8IKK2TcuHHp27dv+vTpI7jzC/yU+3HNNdekffv2ue+++5J8P7NdZYBnyJAhadu2bcrLy9O7d++ccMIJadSo0a9WX1mh8qsoAAAAAAD8DP/pQfjvf//74qDQ8ccfn0033TSPPfbYXO9955130rdv36y55po54ogjsvLKK5fqdAB+dT+1nzzhhBOy3nrr5amnniq+t7y8PNWrV8+kSZNyxBFHpGXLluncufN86yd/GDKqnHGnaq3XX399evfunQ033DCPPvroHO+rqKgohopefPHFLLfccvp0KJGf0vdcf/316dOnT9Zff/05vkdL/q//SZKRI0dm/fXXT7NmzRb4eSyKfuq179u3b9Zaa62MHTt2jvfPnj07NWrUyBdffJHrrrsuf/nLX9K8efNSnMpi4afej5NOOilt2rSZ635Uvv/NN9/MP//5z+ywww6/elsQ3gEAAAAA4Gf7qQ/Ce/XqlY022qg4yFt1cLfyzxMnTkxFRUWWXXbZkp0PwK/t1+gnKwfQZ8yYkSlTpsy3fvJ/qXWDDTYoDvRXDjLP6zhAafxa7blqgIef5te+9vrUX+bX/tr2w9d/LcI7AAAAAAD8LL/Wg3CAxdWv2U8uyMHbylpfffXVPPLII/p0WIT5Hq10XPuFy6hRo9K7d++MGzduobwfwjsAAAAAAPwio0aNSs+ePfP6669n1KhRC92DcIBSqxwwfOWVVxb6MMwzzzyTI444Im+//bY+HRZxY8aMSdeuXfPmm2/mkUceySabbKI9LyD33XdfTj755Lzxxht56KGH9KUlNmbMmHTr1i2vvvpqRo8evVC2BZ8AAAAAAAB+tvfeey9nnXVWXnnllYwePdrABMAPfPDBB+nTp0/GjRu30A4YVtWsWbPUrVs3Dz/8sD4dFnGFQiETJkzIqFGjFvq+Z3Hzyiuv5Nlnn82TTz6pL10IvPTSSxk/fnweffTRhbYtmHkHAAAAAIBf5NRTT82uu+6aDTbYIJMnT86wYcPSs2fPheZBOECpnXPOOWnXrt1CMWBYUVGRatWqzXNbZQ0zZ85MrVq1MmnSpAwfPlyfDgupH2vP5eXlqV69epJk0qRJady4cbE99+rVK7/73e+051/ox5YyrHrtP/jggzRv3jyTJ0/O8OHD06NHD33pfPJT7sdHH32UVVZZpfh1uHfv3gtVWxDeAQAAAADgR/2nQd6qD8OT5Jtvvsl1112Xnj17ZqONNsro0aOTlP5BOMD89J/6ycoQTKWqA4YbbLDBAu8nq/bbL7/8ct5///0UCoW0bNky66yzzhz7VatWLbfeemv233//bLbZZvp0WMj8MCQyYcKE1KxZM2uuuWbq169f7JvKy8tTKBRyzTXXpEuXLvn973+vPf9CVa/9d999l6+//joNGzZM/fr1kyQzZsxI7dq1UygUMmPGjFx44YU5/fTTs/HGG+eRRx5J4tr/mqrejy+++CJTp07N7Nmz06pVq7n2nT59eoYNG5bjjjsuG2644UIT3EksmwUAAAAAwI+o+iD8vvvuy4svvpjPP/88rVq1ypFHHll8wF05OPTGG2/knHPOKcmANEApVO0n77333rzwwgv54osv0rx583Tu3HmO4E6SvPvuuznppJPyu9/9boH3kxUVFcVa+/fvn0suuSRffvllkqRmzZo566yzcvjhh6d+/frF/TbddNMccMABGTp06AKtFfjPqrbnM888M1deeWXee++9JMmaa66ZoUOHZvPNN0+S4n4rrLBCtt1224wcOTKJ9vxzVe33L7rooowYMSL/+te/suaaa2bLLbfMkCFDUrt27SRJWVlZ6tSpk88//zzNmzcX3JkPqt6P888/PzfddFNee+211KpVK3vttVe6d++eli1bFkO2derUyQcffJCtt956oWsLZt4BAAAAAGAuVWeS6NWrVy644ILMmDEjNWrUyOzZs7PNNtvk5JNPTtu2bed42H3VVVflkEMOSbLwPAgHmB+q9pM9e/bM4MGDM3369GI/ueGGG+byyy/PeuutN8fMPMOHD8/f/va3JAuun6y6nEiPHj0yaNCg7LrrrjnooINSu3btDBo0KGPGjMlJJ52UY489Nssss8xcS5Do02HhULVtnnDCCTn//POz+eabp0OHDvn6669z2WWXZebMmRk8eHD+9Kc/FYMkVWnPP0/Vfr9bt2658MIL06pVq2y55ZZ5++23M2bMmGy33Xa5/vrr07Rp03nOzOba/3rm1RZat26dP/zhD5kwYULuvvvubLPNNjnxxBOz1VZbzXOWvIXpfgjvAAAAAADwo0466aQMGDAg+++/f4466qist956Oe+889K3b99sueWW6dOnT/7whz/M9dD7h0tqASxOqg4Y9u3bN2eccUYOOuigHHbYYWnbtm369++fU089Nb/73e9ywQUXZPPNN18oBnCvuOKK9OjRI3/729/SpUuXrLHGGikvL0/r1q3z73//O4VCISeeeGK6d++eZZZZZq5zBRYeQ4YMSe/evXPQQQelY8eOad26db755pust956+eCDD9K4ceNcdNFF+ctf/lIM8GjPv45Bgwbl5JNPzmGHHZYjjjgi66yzTt59991suumm+eqrr7LlllvmpptuyoorrjjH98T/aZlFfr7K/5scfvjhOeyww7LOOuvk3//+d9q2bZvPP/88bdu2zWmnnVYM8FS2g4WtPfhkAAAAAACQ5PsBnapGjBiRq666KkcccUROOumkbLrppqlRo0aGDRuWhg0b5l//+ld69eqVRx55JLNnz57jGII7wOKscrBvxIgRufrqq3P44YenT58+adu2bSoqKnLzzTencePGeeWVV3LUUUdl7NixKS8vn+MY8yu48+9//7u4HFZVn376aYYNG5YNNtgghx9+eNZYY41Mnjw566yzTr799tucfPLJ2XLLLXP22Wdn8ODB+eKLL+Y4V2DB++yzzzJjxoy5Xn/nnXdy/fXXp127djniiCPSunXrTJo0KZtuummmT5+eLl26pEGDBjn22GNz++2357vvvkuiPf8vZs6cOc/Xn3322Vx33XXZfffdc/TRR2edddbJpEmTsscee6R69erZfvvt8/jjj2e//fbLp59+Osf3xII7P9/kyZPn+jqaJC+//HKGDRuWHXbYIR07dsw666yTKVOmZJdddkm1atWy77775vnnn0+/fv0yevTolJeXF9vBwtYefDoAAAAAAJZgzz33XO67777MnDlzjgfY3377be6+++4ss8wyOfzww9OqVatMmTIlv/3tbzNp0qQMHDgwffv2zcsvv5zTTz89Dz/8cGbPnr3QPQQH+KWeeeaZjB49OhUVFXO8PnXq1Nx8881Zeuml07Fjx7Rs2TLffvtt1l577WI/2aNHj7z++uvp2rVrxo4dO9cxfm2vv/56WrVqleOOOy5TpkyZY1udOnXy73//O/vuu2/WXXfdTJs2LTvssEMmTpyYQYMGpV+/funRo0eS5LTTTsuFF144zxAQsGA8//zzWWONNXLZZZcVQ9KVpk2blqeffjqHHHJI1l577UybNi3bbrttvvrqq5x33nkZOHBgjjrqqEycODFdu3bNnXfeOc8QEPM2ZsyYbLXVVnn//ffn2vb222/n1VdfzVFHHZXWrVtn6tSpadeuXb788stcfPHFufzyy7P11lvn0UcfzQEHHJAJEyYs+BNYzIwdOzbbbrtt7r333rm+jr799tt55513cswxx+Q3v/lNpk6dmi222CITJ07MueeemzPOOCP77rtv/vnPf+bss8/OmDFj5hkCWhgI7wAAAAAALKE+++yz7LLLLmnfvn1effXVObbVr18/DRo0yNFHH50NNtgg3333XXbbbbd8+eWXGTBgQI488sgceeSRWX311fPEE0/kjDPOyH333bfQPgwH+DkmTJiQ3XffPdtvv31ef/31ObbVq1cva6yxRg488MCsv/76+e6777Lrrrvmyy+/zJlnnpnDDz88ffv2zVprrZXnnnsuxxxzTEaPHj3XLGe/phkzZmSLLbbId999N9fMPksvvXRefvnlHHzwwamoqMjJJ5+cV199Nd27d8+f//znJMmOO+6YTTbZJK1bt86ZZ56ZSy+9dL7WC/y4Tz75JDVq1MjTTz89Vzv87W9/m1deeSV77rlnZs+ena5du+btt99Onz59sscee6ROnTrZb7/90qxZsxQKhey777655557SnQmi5by8vKMHDky//rXv3LZZZfNde07dOiQkSNHZuutt87MmTNz8MEH5+OPP07//v2zyy67pHnz5unSpUtq166d0aNHZ5tttsnnn39eorNZ9JWXl2fcuHF5/vnn8/jjj891P/baa69cf/312WabbTJz5swcddRRef/999O/f//stddead68efbYY48kyaOPPpojjjgiTz/9dClO5b8qK/iKCwAAAACwRPr2229z1VVX5aWXXsr555+fhg0bpqKiYo4p/WfPnp0aNWpk8ODB6dWrV0444YT07NkzSy21VJLkmGOOyVNPPZVnn302f/jDH3LPPfekdu3apTolgF/VtGnTctFFF+Xtt9/Oeeedl4YNG/7ovhdffHF69OiRE044Ib179y72hUcffXSeffbZvPDCC9lss83y8MMPp1atWvOt5vfffz/LL7986tatm5EjR2aTTTbJ8ssvP8c+hUIhbdu2zdSpUzN27NjUr18/STJ9+vQ0b9487du3z6xZs9K7d++suuqq861W4D979tln85vf/CYNGjTIc889l7XXXjt16tSZY59vvvkmm2yySVZcccU8+OCDqVmzZpLvwz9t2rTJgQcemDfeeCNXXXVVmjVrVorTWOR8+eWXue+++7LbbrulcePG+eabb+b4Prny91dffTVbbbVVdtxxx1x99dXFvv3hhx9Ohw4d0q5duzzzzDN57rnn0rRp0xKf1aLr66+/zrPPPptNNtkkjRo1yrvvvpsWLVrMtUzvv//972y11VbZZJNN8o9//KP4+rhx47LLLrvkz3/+cx566KGMGjUqK6ywwoI+jf/KzDsAAAAAAEuo+vXr54gjjsill16ahg0b5vzzz89dd92VWbNmFfepnLnh6aefTu3atXPYYYcVgztJ8sQTT+Q3v/lNbr755lx77bWCO8Bio1AopG7duunatWsuueSSNGzYMIMGDcqDDz44z+Wvnn766dSsWTOHHHLIHH3hc889l1atWuWqq67KjTfeON+CO5U/r9+iRYvUrVs3w4YNy+67754zzzwzX3311Rz7fvXVV3nzzTfTtGnTYnAnSa655prUrVs3nTp1yiWXXJJVV13VjGpQApV9zEYbbZQGDRrkggsuyMYbb5wbb7xxruWvPvroo7z11ltZe+21i8GdJLnpppuy4oorpkePHhk5cmSaNWumPf8EFRUVWXbZZbP//vuncePG6dGjRzbccMN8/PHHxYB75e9vvfVWJk6cmD333HOOvv3+++/P2muvncsvvzyvvvpqmjZtOt+XTVycLb300tl+++3TqFGj9O/fP9tuu20effTR4ue58uvfa6+9lk8++SQbb7zxHO8fMWJE6tevn65du+aZZ57JCiussFDeD+EdAAAAAIAlVEVFRerUqZOaNWvmueeeS7du3dK7d++MGjWqGOCpfBg+efLkOX5PkuHDh2fixInZY489svfee2fllVc2KAQsNsrKylJeXp5atWqlVq1aefrpp9OjR4907Ngxjz/++FwDf7Nnz57j9yQZNmxYxo8fnz333DP777//fO0nq9ZTUVGRDTfcMHvuuWcuvvjinH766fnyyy+L2xs1apQddtgho0aNyvnnn5933303Q4YMyQUXXJBlllkmK6ywQnFw+oczGwDz3w8Xz1l99dXTpk2bHH/88XMFeFZeeeVsuOGGuf766zN27NhMnDgx1157bYYOHZqmTZumcePGxWCJ9vzfVb32M2fOzJQpU/Luu++mQ4cO+fjjj+fYZ6WVVkq1atUydOjQ4ntuueWW3HnnnVlxxRXTpEmTNGrUKIVCYY6ZLfnpfvg1s2HDhpk4cWJ69+6d0aNHp7y8PGVlZUmSddddN02bNs2DDz6Y8ePH57vvvsuNN96Ym266Ka1bt86KK66YunXrLrT3w7JZAAAAAABLoPLy8uIAzoQJE7LCCivk6quvTp8+fdKkSZMMGjQo2223XfEnuK+88socffTRadu2bQ499NC89tpruemmm1KrVq089thjlgIAFjs/7CeXX375XHHFFTnllFNSt27dDB06NO3atSsOAF5yySXp3Llz1ltvvfTu3TvPP/98br311tSoUWO+95OFQqE4eHnmmWemRo0aOf744/P666/n9NNPz6233ppjjz02ffr0ybLLLpskeeCBB4r7VGrVqlUeeuihNG/efK5lFIEFo2rbGzJkSJo1a5Y99tgj99xzT0466aS88847ueiii/LXv/61OMvXBRdckJNOOimzZ89OgwYNMmnSpKyyyip59NFH07x58zn6CH5c1Wt/zz33ZOONN85SSy2VM844I4MGDcrGG2+cESNGFJcfmzJlSv7617/m3nvvTZs2bdKoUaOMGzcuTZo0yeOPP55VVlmllKezyKt6P6677rpsuOGGad26da688sr07Nkzq622Ws4+++xsvfXWqV69er7++uucfPLJufjii7Pmmmumdu3aee+997LMMsvksccey6qrrrpQtwVfcQEAAAAAljCFQqE4IN2jR4/st99+efvtt7PPPvvkzDPPzBdffJHu3bvn4YcfLs7As/POO6dz58559dVXc8ABB+Sss85Kw4YN88ADD1gKAFjsVO0nTzjhhBx++OF5++23c8ABB6Rv376ZMmVKDj/88IwZM6Y4K8ABBxyQzp0758MPP0yHDh0ycODA1KtXb4H0k5UDkQMHDsxJJ52Uzz77LF9//XXWXXfd9O7dO3vvvXcuvPDCnH766fn888+TJDvuuGOuuuqqDB48OIceemjOPvvsjBkzJs2bN095ebngDpRIZdvr169funTpkmeeeSZTp07NLrvsklNPPTWtWrVKly5dcuONN2batGlJkuOOOy5Dhw7NgQcemPXXXz+dO3fOE088UWzPC2tYYWFTee27deuW3XbbLXfeeWcaNGiQE088Md26dcvTTz+dv/zlL8UZeBo0aJBrrrkmRxxxRCoqKjJhwoRst912xeCOGSl/mcr7ceKJJ+bwww/PiBEjkiTt27fPgAED8t577+XEE0/M6NGjM3v27Cy99NI54YQTctZZZ6V27dqZNWtWdtpppzz++OPFZSAX5rZg5h0AAAAAgCXU0KFD07Fjx3Ts2DHHH398VltttXz77bcZMWJEevTokeWWWy5nn312tt1229SuXTtff/11xo8fXxwM2mijjbLsssvOMTsFwKKu6k/lX3755Tn66KNz5JFH5sQTT0zz5s0zbdq0XH/99Tn55JPToEGDDB06NG3btk2tWrXy3Xff5Y033sjLL7+cFVdcMRtuuGGWWWaZ+dZPVj3urFmzsvnmm6dVq1Y544wzstpqqxX3e+mll3LmmWfm1ltvzTHHHJNevXr96ExA+nQojcq2VygU8sknn2T77bdP27Zt07dv37Ro0aK43913351+/foVZ+DZe++9U69eveL22bNnp1q1aqlWrZr2/BNVvU6PP/54/vrXv2bnnXdOz549s/rqqydJJk6cmLPOOivnnHNONtlkkzlm4Jk1a1amTJmS8vLyNGjQIHXq1HHtf4Gq1+7ZZ59Nhw4dsuOOO6Z79+7Fr22TJ0/OTTfdlF69emW11VbLwIEDs9VWWxVnDZ01a1bxODVr1lwk7ofwDgAAAADAEuKHS6Dsu++++eyzz3LFFVdktdVWKw5YzyvAs91226VWrVr/9ZgAi7If9mkHH3xw3n333Vx11VVp1apVcfu8Ajybb755cdDwPx1zfjj77LPTtm3bdO3aNQMGDMh2222XQqGQQqFQ/Ld/GODp06dPlltuuflaF/C/GzFiRNZbb73stNNOGTZsWDbddNMkc/YlPwzwdOjQIUsttVQpy14svPnmm/n8889z4IEH5p577slaa62V5P+u/X8K8FS1MC/NtCh544038vHHH6d9+/Z5+OGHs8EGG8yxfV4Bnm222WahD+n8mBqlLgAAAAAAgAWjcsCne/fuWWqppfL5559n3333zWqrrTbHgFD9+vXzl7/8Jcn3y2qdeOKJOeecc7LtttvONTAtuAMsTir7tM6dO6esrCxff/11DjjggLRq1aoYhCkUCqlbt27233//JMnJJ5+cww8/PFdccUW22GKLuQYN53c/+cADD6Rnz56pU6dO6tWrl4YNGyb5fimtsrKy4iDyeuutl969eydJhgwZkilTpmTQoEFp0qTJfK0P+Omuu+66HHTQQWnWrFnq1q2bNddcs7itsv8pKyvLbrvtluT7pbWOP/74fPfddzn00EPnGbTmpzn55JNz2mmn5Xe/+11WX331YnAn+b9r36RJk/Ts2TNJcu6552afffbJ8OHD07x58zmOJbjzy51//vnp1q1b2rVrl4022qgY3KkajGrUqFH22WefJEmvXr3Sp0+f9O/fPzvuuOMi+X+URa9iAAAAAAB+tnfeeSe33XZbTj/99DzyyCP58ssvk8w9uFwZ4Bk4cGAmTZqUgw8+OE888UQpSgZYoD744IPccccdGTJkSO69995MmDBhju2VgZjKAM8pp5yS6dOn509/+lOefvrpBV7vDjvskG7duqV+/fqZPHlyXnvttSTfLztStd4kWW+99dKnT59su+22uf/++xfJwU1YnG255ZbZeeed8/XXX+fTTz/Nq6++mmTe7Xm33XbL6aefngYNGuSCCy7IrFmzSlb34mCNNdZIo0aN8tprr+Xrr7/Od999l+T7WXeS/7v2lQGe7t27Z+zYsencuXNxH349q6yySjbYYIM8+eSTefHFFzNu3LgkcwejKgM8AwcOzDPPPJPBgwcvsm3BV2QAAAAAgCVIq1atMmTIkGyzzTapVq1aXnjhhUycOHGe+1YGePr06ZOmTZumdevWC7hagAWvefPmGT58eDbeeOPMnj0748aNS0VFRcrKyuYaQK8M8Bx//PFp0aLFXLMvzG+zZ89OWVlZzj777Bx88MEpLy9Pr1698uabb6Z69erzHPD/7W9/m/POOy8vv/xyGjduXHwdKK2Kioqsttpqufjii7P11ltnypQpOe2005Ik1atXz+zZs5PM2Z532WWXXHHFFXnsscdSr169ktW+ONhvv/1yxRVXpFatWnnhhRdyySWXJPk+4D6vAE/37t1z6qmnZsiQIYKQv6LKa/2Xv/wlffv2zYYbbpgvv/wyt912249+vWrUqFH23nvvXHfddRk6dGhq1669IEv+1ZQVfEUGAAAAAFgsVZ1WPskcS2Pdd999Oe200/LUU0/lzDPPLC4BMC9Tp05NktSrVy/l5eVzLQkDsKj6T/3kY489luOOOy4vvfRSevfundNPP32ufSrf/91336WiomK+9pNV/9151V8oFNKrV6+cffbZadasWf75z39mlVVWmaOe/3S+wILz39re+++/n2OOOSYjR47Mn/70p9x2221Jvg/s1ahRI8nc7dn3aD/Nf7v2t912Ww444IBMmzYtV1xxRQ455JC53lf558p74Nr/fP/tftx5553p379/XnrppVx66aU54ogjfnTfRf1+CO8AAAAAACyGqj60njlzZqZPn56Kioo0bty4uM/999+ffv365dlnn815552X4447rjTFApRA1X5y9uzZ+fbbb1OtWrU0bNiwuM9jjz2WY445JuPGjUv//v3Tr1+/JPMO8Pzwz/Or1n/84x954YUX8t5776VJkyY5/PDDs+qqqxZn0endu3cGDhyYlVdeOWPHjp0rwAOUVtX2+Mgjj+TNN9/Ml19+meWXXz777rtvateunVq1av2kAA//m6rX/o033sj48eNTKBSyzDLLZP311y/uN2LEiBx66KGZMWNGLrnkknkGePjlqt6PF154IR988EG++eabtGnTJuuss07q1KmT5PsAT79+/TJu3Lj/GuBZlAnvAAAAAAAsZqo+CL/00kvz4IMP5tVXX02tWrXyt7/9Ldtvv3023HDDJMkDDzyQvn375tlnn825556brl27lrJ0gAWiaj/597//PQ888EBeeOGF1K9fP3vuuWf23XfftGnTJkkyZsyYdOnSZa4Az4IKxFQdLO7evXsGDx6c6tWrp169evnqq6/SqFGjdOvWLXvttVfWWmutFAqF9OnTJ2eddVaaNWuWsWPHplmzZgI8sBCo2p579uyZiy++ONOmTStuX3/99XPCCSdkp512ytJLL50PP/wwXbp0yd133z1HgEd7/t9Vvfb9+/fP5ZdfngkTJhS3H3fccTnqqKOy5pprJvk+KHnIIYfMFeCZXyHNJU3V+9G3b99cdtll+fLLL4vb99133xx88MHZdtttkyQjR45Mnz59FusAj1gYAAAAAMBipFAoFAdzunXrls6dO+fFF19My5YtU7t27Zx00kk54YQTct111yVJdtxxx5xxxhnZaKON0q1btwwePLiU5QPMdz/sJyuDORtuuGGWWmqpDBo0KMccc0xuuOGGFAqFtGvXLpdccknWXXfd9O/fv7h81oIaOK8c3BwwYEDOPffcHHLIIRk7dmy++OKLPPDAA1lttdXSr1+/3HnnnZk1a1bKyspyxhlnpGfPnvn444+zxhprZPz48Qb6YSFQ2Z5POeWUnH322Wnfvn3Gjh2bCRMm5NJLL8348ePTuXPnjBw5MuXl5Vl11VVz0UUXZbfddssdd9xRDDJoz/+7qqGpU089NZtuumlGjBiRf/zjH9l7770zePDgdOnSJS+++GKSZK+99spVV12V2rVr59hjj82QIUOSRHDnV1J5P/r06ZMzzjgjW221Ve6999489NBDOf7443PrrbemS5cuefDBB5Mku+66a84444ysu+666dSpUy688MJSlj9fmE8LAAAAAGAxUjmgMGTIkFx44YU5+uijc/TRR6d169b5/PPP06dPn1x55ZVZfvnl0759+9SpUyfbb799kuTkk0/Occcdl7p16+awww4r5WkAzDeV/eQll1ySwYMHp2PHjunYsWPatGmTDz/8MKeffnquuOKKrLDCCtlrr71Su3btbL755hkyZEiOPfbY9OvXL/Xq1VugM5W99dZbufbaa7PtttumW7duadWqVZJk4sSJmTBhQpo1a5YjjjgiNWvWLC6pc8YZZ+Tbb7/NzTffHAtxwMLj6aefzhVXXJE99tgjvXv3Lrbn+vXrZ+bMmWnQoEF22223VK9ePRUVFcUAz6RJk/LUU0/lyy+/zLLLLlvis1g0jRw5MpdddlkOOuig9OnTJy1btkySTJgwIbfeemveeuut4v1Ivg/wVKtWLXvttVfOPffcHHTQQalXr16pyl/sPPzww/n73/+efffdNyeffHLWWGONVFRU5Isvvkh5eXmSZNNNNy3uv+uuu6asrCxHHnlkzj777Bx22GGL1f0w8w4AAAAAwGKkUChk8uTJ+cc//pG11lornTt3TuvWrVNRUZFHH300o0aNSvPmzXPJJZekTp06mTlzZpJk++23T+/evbPTTjsVwzwAi6NCoZCpU6fm1ltvzVprrZUuXbqkTZs2KS8vz9NPP50HHnggzZs3zwUXXJDatWsXBxC32GKLnHPOOdluu+2y5557LtCax48fn7feeivt27dPq1atMnv27Nx0003p0aNHateuneeffz5NmjTJrFmzikvwlJWV5cILL8xbb72VlVdeuXgeQGn9+9//zieffJIDDzxwjvbcu3fvLL300nnuuefSuHHjzJo1K7Nnz06SrLrqqrnhhhvy/vvvZ9lll01FRUWJz2LR9Nxzz2X69Ok59NBD07Jly8yaNSs333xzBg4cmJYtW+aZZ55J/fr1M2vWrOJ79txzz4wcOTJjxoxZrIIiC4MXX3wx3333XTp27Jg11lgjs2bNyq233poePXqkefPmGTNmTBo2bJhZs2Zl6tSpSZJddtkl11xzTZ577rnF7n4I7wAAAAAALEbKysry9ddf5+mnn87WW2+dNddcMzNnzsytt96aE088MYVCIc8880yWWWaZJMknn3ySyZMnJ/n+p1lvu+22NG/evDhYBLC4KSsry1dffZWxY8dm2223zZprrpkZM2bk1ltvTbdu3VKtWrU888wzxQHy9957r/jebbbZJiNHjkyLFi3mWz9Zedyqs+V8+eWXSZIWLVokSW655Zb06NEjZWVlefrpp4uzcHzyySfZZZdd8uGHHxbPtVGjRnMsFQYsOJWhuUKhUAzcvPHGG0mS9ddfP8mc7fmpp54qtufXX389Xbp0KQZJVl555Sy33HKpqKgoLjnEj6vah5aXl6dQKOSpp57KCiuskM033zwVFRUZMWJETjzxxJSVlWXs2LHFaz927NgMHz68+P6dd945zZo1E4L8BX54P5LkySefTP369bPhhhtm5syZxftRrVq1udrCTTfdlBkzZiRJtttuu6ywwgqL3f3QqgEAAAAAFiE/XPqk6k8GV6pWrVrKyspSp06dJMltt91WfBBedZB3ypQp2XHHHXPXXXcV31u7du0kSY0aNebXKQDMV/+tnywUCllqqaVSs2bN4r533nlnevToMVc/OXPmzLRr1y6XX3558f21atVK8uv0k1Vnz6icCa3yuE899VRx23LLLZckueGGGzJs2LD06tWrWGvltiQ599xz89xzz+Xzzz+f49+pXCoMmH/mNRtOZWjus88+KwZuVl999STJiBEjcvvtt/9oex40aFBuuummYhivkuDO3H547SsqKor9XmV4saysLCuuuGK++OKLPP7447nrrrvSs2fPeV77/v37p3///pk0adIcxxWC/Gl+eD/Ky8uL92PGjBnF67jeeutl8uTJefrpp/Poo4/+6P3o1q1bBg4cmG+//XaO4y5u90PLBgAAAABYRBQKheKD748//jhJUrNmzSTJ0KFDM3HixCTfB3BWWWWVDB8+POecc05xQPpf//rXHA/CzznnnEyYMCENGjRYwGcCMH9U7SfHjx+f5P/6yXPPPTfTp09PWVlZKioqsuKKK+aOO+7ImWeemRNOOGGufrJQKGTAgAGZOnXqHH3nr6lyEH6//fbL8OHDiwOeRx11VDbffPM8/fTTSZItt9wym266aa699tp069YtZWVleeWVV+aoddiwYbnzzjuz1157pU2bNvOlXuDHVbbnHj16ZPTo0cXXDznkkKy55pr57LPPkny/BF/Dhg1z9tlnp3PnzqlWrVpefPHFOfqZq666Ko8++mj+9re/pVmzZgv0PBZFldf+5ptvzsSJE4t/P/roo9OhQ4diUHPbbbfNtGnTcsopp+TYY48tzrRW9doPGTIkr7/+evbbbz/fI/9Mldf/oosuymuvvVYM2Rx88ME54IADMn369CTJWmutldmzZ6dTp0459NBDU7169bn+vzJkyJC89tpr+fOf/5yGDRsu+JNZgIR3AAAAAAAWEZUD0ttvv326dOlSXMrl2GOPzZFHHpm77747SdK0adPsv//+mTBhQvr375+Kioq88cYbWX755ZN8/9OwN998c6677rq0a9cu22yzTWlOCOBXVtlP7rDDDunSpUtxxorjjz8+3bt3z9ChQ5N8308ef/zx+fDDD3PqqaemUCjk5ZdfnqOfvPXWWzNs2LBsscUW+cMf/jDfan788cdz22235fTTT8/DDz+co48+OkOHDs2xxx6bFVdcMcn3A6EDBw7MmmuumS+++CKHHHJI6tWrVzzG0KFDc+qpp2appZbKwIEDU7du3blmIALmvxtvvDGDBg3KwIED8/bbb+eYY47Jddddl/32268Yzlt99dVz9tlnZ9q0afn0009zyimnpFGjRsVjXHvttRk4cGAaN26cvn37pnbt2trzT3DiiSfmr3/9a66++uok339/fOmll2aVVVYpBtx333337L333nnkkUfy1Vdf5ZZbbinOtJZ8P7vZhRdemFVWWSUdO3Zc7GZ2WZDOPPPMHHvssbnssssyadKknHDCCbn22muzzDLLZNq0aUmSvffeOwcddFBeffXVfP3117nmmmuKX4eT79vT4MGDs8IKK6Rr167FMO7iyrynAAAAAACLkK+++ipLLbVU7r777jRr1iyTJk3K8OHD071792y99dbF/U444YS8+eabue6667LJJpvk448/TosWLZIk559/fv7+97+nUCjk0ksvTaNGjVJRUWEZBmCR9cMZdxo0aFDsJ7/++usMGzYsPXr0yO677158T/v27fPSSy/l8ssvT5s2bfLuu+9mnXXWSXl5eS6++OJccsklKS8vz2WXXTZf+8nf/e53uemmm9K9e/e0b98+33zzTY477rj07t17jkHlDTbYIP369Uvfvn3Tv3//3Hfffdloo43y0ksv5YUXXkjTpk3z8MMPZ6WVVkp5eblBZyiBdu3aZeDAgenfv3+23377fPjhhznhhBNywgknzBFK2G233TJhwoQMGDAgPXv2zOjRo/Pb3/42o0ePzmOPPZbGjRvngQceyIorrqg9/xeVffO2226bl19+Ob169cptt92WJ598Mt27d8+xxx6bZZZZJknSoEGDHH744Zk8eXJGjRqVIUOGZKuttkrr1q0zbNiw/OMf/0itWrXy0EMPZfnll/f98S/Qvn37vP7667n44oszevTojBs3Lr17907Hjh3TpEmT4ue6f//+mTJlSv7xj3/kxBNPzAEHHJDll18+9957b+6+++4stdRSefDBB9O0adPF/n6UFcT0AAAAAAAWalOnTi3OsFAoFPLVV1/l5JNPzqWXXppCoZBDDjkkZ599dpo0aTLHAPYHH3yQk08+Odddd12qV6+eddddN1999VU+++yzrLnmmrnrrrvSokULg0LAIu/bb79N/fr1i33gBx98kPPPPz8XXXRRCoVCDj300Jx++ulzDf69+uqrGTx4cIYOHVrsJydOnJjPP/88a6yxRu666640b978V+0nR40alWHDhhVnh6i066675t57702jRo0yYMCAHHXUUUkyR73Tp0/PO++8k969e+f555/PhAkTsu6662arrbZKjx49DPTDAvboo4/m/fffz8EHH1x8bdasWdlyyy3z9NNPp3nz5hk8eHB22223JJmjfX755ZcZNWpUevbsmQkTJmTGjBlZbbXVsuWWW+aMM87IyiuvrD3/B88//3zq16+fNddcs/jauHHjsvPOO2f8+PHZYostcvnll+c3v/lNkjmv/eOPP54rr7wy1113XfG9jRs3zmabbVacrce1/98888wzqVu3btZee+3ia1OnTs0666yTjz/+OOuuu24uuuiibL755knm/No2YcKEnHrqqbnsssuSfP//neWXXz6bbbZZLrroojRr1myJuB9m3gEAAAAAWIj985//zN/+9rfccMMNadu2bcrKyrLsssumZs2axUHqqVOn5ttvv02TJk1SVlZWfL158+a55ppr0rZt24waNSpvvvlmNtxww7Rr1y5/+9vfstxyyy0RD8KBxdvjjz+eP/7xj7n//vuz5ZZbJkmaN2+e2bNnF/vDadOmZfbs2Um+X4KqctBw7bXXzpAhQ7LNNtvkxhtvzMcff5zf/e532WqrreZLPzl9+vScddZZGTVqVDbaaKN06tQpFRUVefPNN/PNN99kjz32yHPPPZeBAwemSZMm2XXXXYtLYJWVlaVOnTpZZ511ctddd2XixImZOHFiWrZsmdmzZ6dmzZr6dFiAJk6cmIMOOigfffRRmjdvXlxe78knn8zEiRPTtm3bPPvssxkyZEiWX375bLLJJqlevXqx/1l22WXToUOHbLPNNpk0aVI+/fTTrLvuuqlbt27q1KmjPf8H7777bjbaaKM0bNgwr7/+enGJwWeeeSaff/55Vl555fzzn//MPffckxVXXDENGzac49pvueWW2XLLLXPQQQfl008/zTfffJNNN900q6++eho0aODa/48++uijbL311pk1a1befffdrLLKKkm+D6tOnjw5zZs3z4svvphbbrklTZs2TatWrVKtWrXi17YVVlghl1xySfbff/9MmjQpX3zxRTbeeOOsuuqqqVev3hJzP8y8AwAAAACwEDvnnHNy4okn5ogjjsgll1ySJJkyZUoGDRqUmTNn5v3338+IESNy8MEHp3v37mndunWS739itaKiYo4H3TNnzkytWrWKf1/cp54Hlgznn39+unXrln322SfDhg1LRUVFZs6cmZNOOinJ9z/Rf/PNN+eQQw7JiSeemDXWWCPJ3H3gvPrE+dFPjhs3Lrfffns6d+6cJk2aFF9//fXX06BBg7z44os55phjUlFRkUGDBmXXXXfNUkstVRy8rFrT7NmzU6NGjTlmXQMWnOHDh+fxxx/PgAEDsvTSSxdf/+c//5nGjRvnvvvuS9++fbP55pvnjDPOyO9///sk/9d2q7bnqu1Ym/7vOnTokO+++y7Dhw9PgwYNknw/6+Tjjz+eunXr5rLLLsuoUaNy+umnp1OnTsV9Kq/9j3Ht/3fTp09Pnz598umnn+bSSy9Nw4YNkyTvv/9+Xn/99Sy33HK58MILM3z48Bx11FHp1q1bWrZsmST/NZizJN0P4R0AAAAAgIXYrFmzirNJNG7cOB9//HGaNWuWb775JmVlZZk9e3a6deuWa665Zq4AT/L9A+/y8vLUqFFjngO/AIuDkSNHZosttkjjxo3z0UcfZZVVVsm3336bJJk0aVJOOeWUXHXVVTnkkEPSo0ePtGrVqvjeQqGQQqGQatWqFUOO86ufrByErPy9c+fO+eijj3LnnXcW9/nuu+9yzz335MQTTywGeHbeeefUq1cvhUIhd911V2bPnp299trrV68P+GmqBgoqwyBdu3ZNixYtcuyxxxb3+/zzz3PFFVfktNNOyxZbbJHTTz89G220UfH7sYcffjgtW7YsBhn476qGPb777rsstdRSueCCC7Lttttm3XXXLfbfY8aMySmnnJLHHntsrgBPoVDIiy++mPXWW8/3xL9Q5fWeNWtWKioqUrt27QwaNCjbb7991l9//eL2Dz/8MD169MjNN9+cjh075vjjjy9+7ivvxxprrJH69euX+IxKxycRAAAAAGAhVVFRkZo1a2a33XZL48aN069fv7Rs2TLPPvtsGjZsmAYNGmTppZfOaaedloMOOihXX311Bg0alNdee614jDvuuCPHHXdcvv766+JAh0EKYHFRXl6eJNl1113TuHHj9O7dO82bN8+//vWv1K9fP/Xr10+zZs1y4okn5tBDD81VV12VgQMH5q233ioe4/bbb8++++6bGTNmFGcnm1/9ZEVFRZKkrKwsX331VcaOHZu77747hx56aHGfpZZaKrvuumsGDRqUatWqpXv37rn77rszceLEPPDAA+nWrVv222+/fPvtt/Ez+lAalQHqJKlRo0Zef/31XHLJJenatWuuvvrq4n7LL798DjvssPTr1y9PPPFE+vTpk2eeeSYVFRV56KGHcvDBB+ePf/xjcZk//rvq1asXr/1SSy2VO++8M8cff3z23XffvP3228X+u127dunfv3+23nrrnHTSSbn44oszderUJMn999+fffbZJ0cddVTJzmNxUa1atZSXl6dmzZqpXbt2Ro8enR49emSvvfbKa6+9Vrwfq666agYOHJgOHTrk73//e84999y8//77SZJ77703O++8c7p167ZEt4Mfnw8KAAAAAICS+uHD69q1a6dGjRrZe++9M2LEiGy44YZJkpVXXjmnn356kuTqq6/OrFmzctRRR2XChAnp27dvXn/99fTt23eB1w+woNWvXz9lZWXZfvvtM2rUqGy88cZJkjXWWCMnnnhiCoVCrrrqqpSXl+eQQw7JhAkTctppp2XcuHE577zzstJKK8232qouZfjiiy9m/fXXzy233JJOnTrl6quvTkVFRXHQv06dOtlll11SVlaWnj175qijjkrLli0zfvz41KxZM6+88soSPTsBlFpFRUVx6aXx48dnrbXWyogRI3LMMcfk0EMPTaFQyCGHHJLk+wDPoYcemrKyspx++uk57LDDsvrqq+fll19OtWrVct999/3HZZyYU+WMkpX22GOPdOzYMX//+9/zl7/8JbfeemvWXHPNJMmWW26Zk08+OWVlZTn55JPz0UcfpV69ehk5cmQmT56cnj17luo0FhtVZ0KaPHlyNt9885x++uk566yz8qc//Sm333571l577ST/F+ApKyvLpZdemvfffz+rr756Hn744ZSXl6d79+5LzBJZ82LZLAAAAACAhVDVJVtuuumm/O53v8saa6yRCy+8MP3790+jRo1y++23FwM8SfLpp5+mf//+GTp0aDHos+yyy+aRRx7JaqutZrksYLFStU8bNmxYtt566zRr1iwXXnhhTjzxxNSoUSOjR48uBniS5N133825556bSy+9NNWrV0+tWrWy7LLLZvTo0Qusn+zcuXOuvfba3Hbbbdl+++3z5ptvpnPnzhk1alQOPPDAOWbtmDFjRv71r3+lf//+ef/999OmTZtccsklWXXVVYtL9QCl07Fjxzz11FMZPnx42rRpkzvvvDNdunTJxx9/nCuuuKIY4EmSr776Krfffnt69OiRQqGQNm3aZPjw4WnevLn2/BNVXa6sY8eOqVatWoYMGZLk+771kksuybrrrjtHgCdJxo4dm8GDB+eWW25JtWrV0qZNm9x1111p0aKFa/8LVL0fxx9/fL766qsMGDAgyyyzTM4555yceeaZWXnllecI8CTf/5/ltNNOy6WXXpo6derkN7/5Te64444lvi0I7wAAAAAALMR69OiRQYMG5dxzz03Xrl0za9asDB48OKeeeuo8AzzTp0/PZZddllGjRqV58+bp2bNnVl555Tl+KhZgcdK7d++cddZZOfvss3PCCSckSc4///z07NlzngGeL774IiNHjsztt9+eVq1apVu3bvO1n6waCLrjjjty5JFHZqeddspJJ52UVq1aJcl/DPBU+uyzz9KoUaPUqVNHnw4lUjWscM0116Rr167Zeeedc/rpp2e11VZLkv8Y4EmSL7/8MhMnTsxKK62U+vXra88/w/nnn59u3bpl9913z5AhQ7Lyyisn+c8BnilTpuTZZ5/N1KlT07Zt2zRp0sS1/wWqtoUhQ4akS5cuOfTQQ9OvX7+sssoqmT59es4999wfDfAkyZgxY1KrVq2sueaa7keEdwAAAAAAFipVH1q//PLL2X333bPjjjumR48eWX311ZMks2fPzoUXXvijAZ7KfapVq5Zq1aot8Q/CgcVL1T7t9ddfz0477ZQ//vGPOf744+cYqJ1XgKdyWKysrCwzZ85MjRo15ms/WXVwc+LEibn33nszcODA3HHHHWnZsmUKhUIKhUKqVav2owGeGTNmpHbt2vM8JrDg/HBmrgEDBuTuu+/O9ddfn5YtW86x/ccCPLNmzUrNmjWLx9Cef5of9tH77rtvZs6cmYEDB6Zly5ZzzNbSpUuXDBkyZI4Az7yusxkpf74fXrv9998/n332WS655JK0atWqeL9+LMAzr2vvfiRL5nxDAAAAAAALqcqBibFjxxYHlbt06VIM7pSXl6dGjRo59thjkySnnnpq9txzz2KAp/LBd9Xp5gV3gMVJZZ82evTozJgxI9WqVcsxxxxTDO5UDhp27do1SdKzZ89svfXWxQBP5SBvrVq15jrmr61ysLhLly654447suyyy+a3v/1tMbhTVlaWsrKyFAqF/OY3v8mQIUPSqVOnXHvttalWrVquvPLKOYI7VY8JLFiVwYIuXbrktddey4QJE7LHHnukZcuWxe2V7XqPPfZIWVlZunTpksMOOyxlZWU5+OCD5wjuJNrzT1XZR59++umZPn16HnrooZx77rnFa1+jRo1i33/RRRelUCjkkksuyd57750RI0ZkjTXWmCsAtKQHRX6JymvXvXv3zJo1K2+//XY6duxYnE2uevXqqaioSJ06ddKtW7ckyZlnnpk999wzd9xxR9q0afOjx1ySuQIAAAAAAAuZs88+O1tssUUOO+ywNGnSJOuss05xW+XD8MoAT79+/TJ58uS0b98+Tz31lAffwBJhwIAB+cMf/pB+/fqlQYMGWWuttVJeXp7k//rJJOnatWvOOuuszJ49O9tvv30xGLmg1apVK5988kneeeedYm1J5pgJqFAoZM0118yQIUOy44475uqrry4GkICFxz//+c88+uij+eKLL4phkFmzZiX5v7acJLvvvnsuuuiirL766jn00ENz0003lazmxcG7776bfv365corr8xSSy2VFVZYIcn/Xfvq1asXvw5cfPHF6dSpU8aNG5dtttkm//73v4XZf2XvvfdeLrzwwgwdOjSvv/56pk2bluT72T+T78M4VQM8vXv3zmeffZYtttgib731VilLX2j5XxwAAAAAwEKmbdu2WXXVVfPGG29k0qRJ+eKLL5KkOCBR+TC8MsDTv3//vPfeeznmmGOKAxgAi7N99tknTZs2zTPPPJNvvvkmkydPnmPgtrKfTL4P8Jx99tn55ptvcuCBB2bWrFnFwfX5oWo4p9K5556bU089NVOnTs3NN9+cBx54YI5Zd5I5AzznnntuOnTokGOOOWa+1Qn8d/Nqz88//3x23HHHfPnllxk2bFjGjx+fmjVrFvf9YYBnwIAB2XjjjbPpppsu0NoXdT/sp1u2bJkHH3wwSfLxxx/nxhtvTJI5rn3VrwMXXXRR9t9//0yfPj1169ZdgJUvnn54P1ZbbbXcd999WW655TJlypT861//SvL9LEiV9+OHAZ5OnTplmWWWSf369Rd4/YuCssL8/O4EAAAAAID/qHKZqx96+umns88+++T999/PUUcdlUsuuSRJ5pjyv/K9s2bNylVXXZVddtklzZo1W6D1A8xvP9ZPfvTRR2nXrl0++OCDHHDAAbnqqqtSrVq1efaTSTJ06ND88Y9/zCqrrDLfaq36b//rX/9KoVCYY8B+wIAB6dOnT1q1apWrr746m2++eZIUl9qp+ufKY1Uu8wUsWFXb8+uvv54mTZqkadOmxe277LJL7rvvvuy2224ZOnRoll9++Tn6nKrtevr06alTp472/BNVvY4TJkwozrKTJI899ljat2+fL774IhdccEEx5Fj1PVXv3ddff52ll156rmWz+OmqXrtPPvkkTZo0yVJLLZUkGTNmTP7617/m008/Tf/+/dOvX78kc96Pyj/PmDEjM2bMSMOGDd2PeRDeAQAAAAAokaoPre+555688847ad++fZo2bZpq1arl2WefTfv27fP++++nb9++OeWUU+Z63w8HtT0IBxYnVfu0kSNH5uWXX85xxx1XnEXh448/zuabb56PPvoovXr1yumnnz5H8OWHx5jX338tVfvj008/PUOHDs1HH32UV199NWussUZxwP7UU09N//7989vf/jZDhgyZZ4AHKK2q/cTZZ5+doUOHZsaMGXnhhRfSsGHD1KxZM0myww475OGHH06HDh1y4YUX/scADz9N1Ws/ZMiQDB8+PC1atMgNN9xQ3OfRRx/NXnvtldq1a+eUU07JEUcckeTHAzzuw89X9ToOHjw4V199dX73u9/lvPPOS6NGjVJWVpbHH388++yzTz799NMMGDAgPXr0SJIfbQvux7xZNgsAAAAAoAQqKiqKD8JPOumkHHLIIenatWvGjRuX8vLyFAqFbLTRRrn55pvTokWLnHbaacWfZP3h0jBVCe4Ai4sf9pOHH354TjrppDzwwANJvh9QbNasWZ544omsvPLKGTBgQE466aQUCoVUr159jmVUqpof/WShUCj2xyeccEJOPfXUtGvXLvfdd1/WWmutOZYR6devX04++eS8/PLL6dSpU/75z38miYFMWEhU9iHJ9+25b9++WW+99XLOOedkmWWWSc2aNTN79uwkyYMPPphtt902N998c4455ph8/vnncyzbp13/b6r2+927d0+PHj1Ss2bNbLPNNnPst8022+TWW2/NjBkz0q9fv1x++eVJ5lwysWpf7z78PD9sC7169UqjRo2yzTbbpHHjxsUl4rbccsvccMMNWWmllfL/2LvvwJru/4/jz5sdkZAQM5LYq1rULEqtGjVqFlV7b2IFsfferb2J2Zq1YyulLS2t1dq1R4ise8/vD797v7m2lgR9Pf7BPecen3NOzvvcnM/rfj49e/Zk+PDhALbR8MD+HOh8PJlG3hERERERERERERFJQMHBwYwYMYKmTZvStGlT8uXLZ1tm/VbqwYMHqV279mMj8DxtKhkRkXdJz549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ERERkQRgfRh+6NAhmjZtyi+//EKhQoXo0KED3t7ebNy4keXLlwOwa9cu/P397UbkERH5r9i1axfdu3fnhx9+IH/+/LRu3ZoUKVKwadMmW53cvXv3G1Mnr1y5wqJFiwgJCeH+/fskS5aMmJgYIiIiyJo1K2vWrCEwMNB2HxCRN9dvv/3G2LFjmTVrFgA5c+bk3r17XLx4kcDAQDZt2qTr+TWIjIxk8eLFjBw5kt9//51MmTJRrFgxTp06xc8//4y3tzdhYWEEBga+EXX/v2DPnj20adOGI0eOkCpVKrJnz86tW7c4duwYqVOnZvv27boW/iWFd0REREREREREREQS2PHjx+nYsSObN2+2vZYoUSLy5s3L/PnzCQgI0INwEfnPMpvN/PTTT4waNYqlS5faXk+cODF58uRh3rx5b2Sd/PXXX5k2bRoXL14kUaJEFC5cmJo1a+Lr6/vGtVVEni46OpqlS5eyaNEizp49i5+fH4ULF6ZVq1akTJlS1/MrZh19Jyoqil9++YUpU6bw3XffcefOHbJmzUqRIkUYMGAAadKk0bGPZzdv3qRbt2788ssv/P7777z33nsUKFCAHj16kDp1ap2Pf0nhHREREREREREREZE3xKpVq/j777+5ffs2+fPnJ1++fCRNmlQPwkVE/t93333HjRs3uHbtGgULFiR37txvZJ181kgQGiVC5O1ksVgwDMOu1rxptedd8egUZFeuXOHBgwekTZsWwzBwcXHRsY9nce9d0dHRXL58GX9/f2JjY3F2dtb5eAUU3hERERERERERERFJYM962K1OXhGRxzty43qT62Tcdj9rH0TkzWexWDCZTJhMJl3P8cRa3x893jr+CSPucX/auZF/TuEdEREREREREREREREREREREZEE8mbGkEVERERERERERETecvrepIjI28lisbzS7T16P9D9QST+vOrrWV7c0469zknC0Pl482nkHREREREREREREZGX8OjQ8HGnvDIMA8Mw/tX0LY9u/02eDkZE5EU8OjWgxWLBMIynThf4PK9r+pRH27VlyxZOnTpFREQEqVKlolq1ari4uLzUNCFx17tx4wbJkiX71+0UkeczDAOLxWK7ng8dOsSlS5cwDIN06dKRJ08eu3Vf9nqOiIggUaJEr6fxbzHrMYpb9y9fvszNmzfx9PQkefLk/+i4PWkKQk3X9HzW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jylTplC8eHGGDBlC2bJljWUgr1+/zqFDh3BwcKB48eJkz56dFClSaNk4ERERERERERERkf8A+4QewH/ByZMnuXr1KgB37twxlnWxunLlCvfu3aNq1aqkTZs2TuG3bdu2AHTq1In27dszf/58Pvnkk7d/ESIi8Sx2V5vY82RwcLCxz9y5czl06BB9+vThiy++AJ53B6hQoQKJEyfm+PHjBAUFUbFixTjz7Ju0ePFiNm/ejKenJ23atCFFihRxluSK3eHHzs6OOnXqkDhxYgIDA1myZAlr1qwxOuxkzZqVzZs34+rqquKmyFsS+32WjY0NkZGRJEqUiObNmwMQEBCAp6fnKwM8tra2WCwWnj17Zjy2ePFiLl68iKenJ02aNAFeDqTIn7M+T0uXLiV37twUK1YMeL6soMViwc3NjeLFi7N27Vq2bt1KjRo1EnK47zXrvWH989KlS0RFRdGjRw8jrObt7Y2rqysBAQH079/fCPC4ubnh5uZmvH6xz6l7QURERERERERERCThfZC90V9sNpQvXz6WLVtGvnz5mDBhAoMGDeLBgwfY2dlx6dIlQkJCuHPnDp999hmOjo4vLe3Stm1bxo4di62tLQUKFHiblyIi8tZYgzvBwcHMmDGDx48fv7TPwYMHsbOzo02bNiRKlMh4/IcffiB79uzMmTOH5cuXx1twByA0NBQ7Ozv69etnBHf+akmuJEmS8OmnnxIWFkZwcDAtW7akSZMmjB49mp07d5I1a1YV+kXeErPZbNyve/bsITg4mIEDB3LmzBkSJUrEF198QXBwMA4ODnh6er60hFaZMmWIioqiUaNGzJ8/Hy8vLwYOHEjSpEnjhKt1P/9vZs+eTYsWLShVqhTdunVj1apVwPNwVapUqejbty92dnbs2rUrgUf6/jKZTMa9ERERQXh4OOnSpaNmzZrkypWL6OhoANKlS0fr1q0JDg7m6NGj9O/fn3379hn//3nx/0FvYslKEREREREREREREfn3Prhls/6siGs2m9m7dy8dO3bk0qVL+Pr60rBhQxYvXszw4cPx9vZmxIgRf3mOp0+fkixZMhV5ReS9df78eSpVqkRERASjR4+mcePGpEiRwtjesmVL1q9fz5EjR8iVKxcAixYtwtfXl9GjR9OsWTMgfrpeWCwWrl27RokSJciQIQOHDx9+qZPan7l//z6pUqV65bbY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HBxo2rQpu3fvtnbo0HgdkdRnMpmsIZtJkybx9ttvU7JkSSpVqkTnzp05c+aM9QzKmzcvq1evpmjRooSEhKQI8Ch892zkypWLVatWUa9ePe7cucOQIUOIiIhIEZJKCvDMmTOHyMhIFixYQGJiYhqvXEREREREREREREREJH0zWFTxEnll7d27l8qVKwMPAzxPC7D88ccfjBgxgi+++AKAatWq8eDBA06cOIGbmxthYWF4e3s/93UD7Nmzh99//x1fX182btxIiRIlrNcuXrxIixYtOHToEP369cPX15ds2bJZC/yJiYkYDAbs7OxShAdEJPUkP3P69+/PpEmTKFSoEG+++SYRERGsW7eON998E19fX6pXr279OT137hzNmzfnzJkzdOrUiREjRpAxY8a03Eq681fnfdJ5eO3aNVq3bs3OnTvp1asXgwcPJnv27JjNZmto02QysXfvXvLmzUvOnDlTewsiIiIiIiIiIiIiIiIvFYV3RF5RzZs3Z/fu3cybN4933nkH+GcBHoDZs2ezfPlyjhw5Qvbs2SlWrBiTJk3Cy8srVcIw06ZNo2fPnjRp0oTY2Fh++OEHTCYTNjY21vVfunSJ9957zxrg8fPzI2vWrP94jyKSOiZOnMjQoUPp2LEjHTp0oHjx4pw9e5aqVaty/fp1qlatyogRI1IEeH7//Xdq1KiBra0tR48eJXPmzGm8i/Qj+Rl97do1oqOjSUxMpGjRoinuu3btGi1btmT37t307NmTIUOGPBbgedI7RURERERERERERERE5N9TeEfkFbVp0ybatGmDh4cHwcHBNGrU6F89n5CQwL1798iYMSMWiwV7e/tUK+AeOHCAbt26cfjwYbJkycLhw4fx8fF5LJiTFOA5evQon3zyCcHBwSryi6SBO3fu4OLi8tj58PPPP9OuXTty587NmDFjKFSoEHfv3qVChQpERUVRq1Yt1qxZw+uvv86wYcN44403rO+4ePEi9vb2eHh4KJT3DyU/o8ePH8+iRYs4efIkRqORBg0aMHToUIoUKYKdnR3w1wEefb9FRERERERERERERESeLZun3yIiL6N69eqxcuVKrl+/To8ePVizZs0/ei4p72dnZ0eWLFkwGo3Y29tjsVhSrfNC+fLl+fLLL3nzzTe5ffs206ZN4+bNmxgMBpLnEb28vFi1ahVeXl6sX78+VdYmIimF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Fhsbi52dHV27dqVIkSLcv3+fN998k1u3bjF27Fjat29PmTJlaNiwIQsWLMBgMDB27FhrB54kCu6IiIiIiIiIiIiIiIiIvFjUeUfkHzp27Bh169alatWqTJo0CW9vb+u17777jj59+nD27Fm++OILOnbsaL32aFBHwR0R+S8sFgs///wzpUqVIjExkYEDBzJz5kxGjx7NZ599hr29Pb/99htVqlTh/v373L9/n2HDhhEQEJDWSxcRERERERERERERERGRv6F/fi/yD124cIHr16/z3nvv4e3tjdlsxmQyAdCoUSPGjh0LQHBwMGfOnCEpF/doUEfBHRH5O2az+YmfGwwGSpUqBcD9+/fZvXs3xYoVo3fv3tjb2wOQJ08ecufOzcSJE6lRowYff/xxqq1bRERERERERERERERERP4bhXdE/qG7d+8CcOfOHeDh6BlbW1trSKd58+Z8+umnREREcPv2bQwGQ1otVUTSKZPJZB1rderUKQ4cOMC5c+dITEwEsP56/fp1zp07h6urq/XZxMREpk2bxtmzZ3n77bfZsWMHPj4+1mdERERERERERERERERE5MWk8I7IP+Tl5QXA+vXruXTpkvVzg8FAQkICAAUKFCA2NpZTp06lyRpFJP0ym83WzlxDhw6lWrVqVKpUiapVq9KuXTtiYmKws7PDYrGQO3duChYsSFhYGJMnTyYyMpJ58+bxxRdfUKxYMTJmzGh9r52dXVptSURERERERERERERERET+AYV3RJJ50riapNFYlStXpkWLFmzevJk1a9Zw//596z1GoxGA8+fP4+bmRvHixVNnwSLy0kjquBMUFMSoUaMoVKgQ3bt3x8PDg8WLF/PGG29w7949DAYDRqORSZMm4enpSd++fcmbNy9dunQhPj6er7/+msyZM//l+C0RERERERERERERERERebHon+OLABaLJUXXi19++YV79+7h6emJh4cHtra22Nra8sEHH/DTTz/h5+dHTEwMTZs2pUiRIgCsWbOGdevWUbJkSfLly5eW2xGRdMRkMlnPnrt377Jy5Urat29PQEAAuXPn5t69e/Ts2ZMFCxZQtWpV9uzZQ8aMGSlXrhzbt29n+PDhmEwmcubMSc+ePfHw8EjxThERERERERERERERERF5sRksFoslrRchkhZOnDhBfHw8ZcqUSfF5YGAgM2bMIDIyEldXV9q3b0+bNm0oW7YsAF999RXjx4/n5MmT5M2bl3r16nH58mX27duHra0t4eHheHt7YzabrZ00RESeZs6cOdZuO19++SUVK1YkISEBo9HIvXv38PX1ZdasWZQoUYKwsDBcXFxSPG+xWDAYDAruiIiIiIiIiIiIiIiIiKQzCu/IK+ns2bMUKFCAt956i+DgYEqXLg1AQEAAI0eOpGLFihQpUoSTJ0+yf/9+6tatS0BAAFWrVgVg27ZtrF27lhkzZpCYmIinpyeVKlVi8uTJeHl5qXguIv/K0qVL+eCDD8ibNy8Wi4Vdu3bh6emJjY2NNQh4//59BgwYwMyZMylevDh79uzBxcXFGvBJCu+IiIiIiIiIiIiIiIiISPqi8I68kiIjIwkICGD27Nk0btyYQYMGUaxYMWrWrEnJkiUZOnQoXl5eXL9+nYkTJxISEkKtWrUICgqiWrVq1vecP3+e2NhYsmbNSubMmXF0dFRwR0T+NbPZTKtWrVi9ejUODg7s2LGD8uXLW8+TJwV4PD09OX36NBkzZkzr5YuIiIiIiIiIiIiIiIjI/8AurRcgkhbc3NwYMWIETk5OTJw4EVtbWxo1asQvv/xCSEgIXl5eALi7uzNmzBjs7OwYPXo0ACNGjKBKlSoA5M6dO0WnC4vFouCOiPwriYmJ2NnZsXTpUtq2bcuiRYto3749W7ZswcPDI0WAx9nZmfHjxxMdHc369eu5e/euwjsiIiIiIiIiIiIiIiIi6Zw678grLTIyklGjRjF58mRq1KhBdHQ0YWFhODs7WwvqSYYOHcqoUaOoVasWo0aNolKlSmm4chFJj5I66Dwq6bwxm83WAE/16tVZvnw5OXLkeKwDz4MHD4iNjSVLlizq9iUiIiIiIiIiIiIiIiKSzim8Iy+9R4vlCQkJGI1G6+9jYmIICAhg0qRJACxdupT3338feNhJJ3lnnaQAT+nSpZk9ezblypVLpV2ISHqXPGTzyy+/EBUVRVRUFG+88QaOjo7Ws8ZsNvPRRx+xZMkSqlWrxooVK54Y4IHHzygRERERERERERERERERSX8e/+f/Ii8Ri8ViLXKfPHkSwBrcmTBhAkuWLCFDhgwMGjQIf39/AGbOnMmhQ4cAMBgMJM+3jRgxgl69enH58mXraC0Rkacxm83W4M7o0aNp3Lgxb7zxBg0bNqROnTqEhoYSFxcHgI2NDaGhobRu3ZqwsDBatGhBRETEY8EdQMEdERERERERERERERERkZeAwjvyUksqbNeuXZsyZcpw+PBhALp3786AAQO4efMmcXFxZMuWjZ49e9K/f3927tzJ2LFjOXLkiPUdyQM8kyZN4syZM7i7u2M2m1N/UyKSriQPEfr6+uLv74+npyejR49m2rRpXLx4keHDhzNhwgQePHgAPB7gqV27Njdu3HjiyC0RERERERERERERERERSd/s0noBIqmhVKlSbN++nZYtW1K2bFlWrlxJ3759adq0KQ4ODgBky5aNgQMHYjabmThxIhaLhSFDhlCmTBkMBkOKjheZM2dOUZAXEUku+XmRFCKcPn06X375JT169KBr164ULlyYmJgYxo8fz7lz55g4cSIAffv2xdHR0RrgiYqKIjw8XGFBERERERERERERERERkZeUwZK8pYjISyZ5AX3SpEn0798fi8XCBx98wPz58zEajdauOkkF9sjISIKDg5k4cSLNmzfH39+f0qVLp9UWRCQduXDhAu7u7jg5OaU4f86fP0+bNm3IkCEDU6dOpXDhwty9e5fy5ctz7949unTpwuzZs4mOjqZ///7069cPR0dH4GHnnlu3buHm5vbY2CwRERERERERERERERERSf9UAZSXmo2NDSaTCYDo6GhrUGfnzp2cOHECeFgYTwruALi5uTFo0CD69u3L2rVrGTBgAMePH0/9xYtIunLgwAGKFCnCyJEjiY2NxcbGxtot5/bt28TFxdG9e3cKFy7M/fv3eeONN7h9+zajR4/G19eXcePGcf/+febPn8/EiROtI7QMBoOCOyIiIiIiIiIiIiIiIiIvMY3Nkpeera0tADVr1sTBwYGIiAimTJlC06ZNWbJkCZUrV7YGeJKK425ubvj7+xMVFcX333+Ph4dHGu9CRF50mTJlIkuWLEybNg1HR0f69++Pk5MTAGXKlGHatGlUqlSJxMREfH19OXXqFGPGjKFVq1bY29tTuHBhbG1tuXr1Kv7+/mTKlInu3btb36/gjoiIiIiIiIiIiIiIiMjLSWOz5KX0Vx0qkj4fNmwYw4cPx9vbm6VLl1oL6nZ2dlgsFiIiIsiRIwexsbHEx8fj6uqqrhci8lSnTp2iadOmXLhwgcGDB6cI8CS5desWb775JhkzZiQsLMz6eUxMDBUrVqRjx46sWbOG0NBQvLy8UnsLIiIiIiIiIiIiIiIiIpLKlESQl47JZLKGbP744w+OHDnCyZMniYiIsH4+bNgwAgMDuXjxIi1btmTPnj3Y2dlhNptZv349vXr14ptvvsHZ2RlXV1csFouCOyLyVIULF2bNmjXkzp2b0aNHM378eGJjYwGsI7QuXbrEL7/8Qo4cOazPmc1mvvzyS27cuEGTJk3YsWMHXl5e1rF/IiIiIiIiIiIiIiIiIvLyUucdeakk744zatQoFixYwNmzZwHInj07I0eOpFGjRnh6egIwfPhwhg0bhru7O3PnzuXatWuMHz+eP/74g5MnT5IzZ84024uIpF8nT56kWbNmT+zAk5CQQJUqVbh8+TITJ06kXr16fPvtt4wbNw4PDw/Wrl2Li4tLGu9ARERERERERERERERERFKLwjvyUvL19WX8+PHUqVOHFi1acOvWLdasWcPRo0dp3749/fv3J2/evACMGTOGoKAg4uLisLW1JW/evPzwww/kyZNHo7JE5F+zWCwYDIa/DfB888039OjRgytXrpA9e3Zu3bqFt7c327ZtI3fu3NZ3iIiIiIiIiIiIiIiIiMjLT+EdeeksX76cTz/9lA8++IABAwaQP39+AMaNG8fAgQMpWrQoBw4cwNnZ2frM6tWrOX36NHFxcXTp0gUPDw9MJhO2trZptQ0RSQeeFvB7NMDTr18/nJ2duXfvHidOnGDcuHHEx8eTJ08e/Pz88PT01NkjIiIiIiIiIiIiIiIi8opReEfSnad1pOjevTtLlixh06ZNlCtXjvj4eL799lv69++Pvb09e/fuJVu2bH9bIFfxXESeJvk5ceLECWJiYrh37x6lSpUiS5Ys1lDPXwV4kksKAensEREREREREREREREREXn1KLwj6UryLhd37tzB1dXVes1isZCQkEDZsmXJnDkze/bsISEhgVWrVjFw4EBsbGw4cOAA2bNnB2DHjh1kzpyZMmXKpMVWRCQdS34WBQUFMXv2bG7evEl8fDylS5embt26BAUF4ejoCDwe4EkaoZUU1tGYLBEREREREREREREREZFXl8I7km4kL25/+OGH5MqVi379+uHu7p7ieu3atTl//jybNm3i+PHj9OzZ87HgDkDp0qXJmDEjW7ZssRbYRUT+DX9/f0aPHk2lSpWoXbs2Fy5cYPv27Vy5coXatWvz7bffWrvsnDx5kubNm3PlyhU+++wzhg0bprNHRERERERERERERERERLBJ6wWI/FNJwZ3GjRuzePFili1bxpw5c7h582aK+ypWrMjvv/9OYGAgPXv2xNbW9rHgzsSJE/njjz949913sbe3T9V9iEj6ZTKZrF///vvvrFmzhl69erF48WJGjBjBwoUL2b9/P5UqVWLr1q20bNmSuLg4AIoUKcKaNWtwcHBgxYoVxMfHp9U2REREREREREREREREROQFovCOpCvnzp3jzJkzODs7ExcXx5gxY/jiiy+4efOmNdzTpUsXypcvz+LFi4mJieH7779PEdxZunQps2bNIn/+/Hz00UfW0TciIk9ja2sLwNq1a7lx4wY3b96kZcuW+Pj4AA/DPTlz5mTDhg2UKlWK9evX88UXXwAPR20VLlyYvXv3EhYWRqZMmVDzOxERERERERERERERERFRakHSlbx589K+fXvu379Pp06deP311xk9erQ1wAPg4eFBt27dKFGiBCaTieXLl7N9+3Z+/fVXBg4cSL9+/YiLi2Pp0qVky5YNs9mcxrsSkfRk6dKlNG3alI8//pjMmTNTrFgx4OHoPjs7O0wmE66uroSGhpIxY0Z27twJYA0K5s+fH09PT0wmkzV0KCIiIiIiIiIiIiIiIiKvLoNF/+xf0gmz2YyNjQ0XL16kXr16eHp60qdPH4KCgjhz5gwDBw6kY8eOuLu7c//+fb777jumTJnC3r17re/ImDEj5cqVY+HChXh7e2MymaydNEREniTp7Ely4sQJRo0axaZNm7h16xaLFi2iVatWKYI4iYmJPHjwgMqVKxMREcHhw4fJlSuXOn2JiIiIiIiIiIiIiIiIyGMU3pEXksVi+cuOFCaTiS5duvDtt9+ybt067t27R69evbh06RK+vr7WAI/JZOL+/fssXbqUGzduEB8fT/Xq1SlXrhyurq4K7ojIUyU/i/bv30+5cuWws7Pj9OnTjBgxgqVLl1K7dm1CQ0Nxd3cHHgZ37OzsAHj99dexWCyEh4fj4OCQZvsQERERERERERERERERkReXwjvywkne5SI2NhYnJyfrZ0mBm/Pnz1OsWDG6du3K+PHj2bp1K3369OHChQspAjz/5M8QEXmawYMHs2nTJg4dOmT97NdffyUoKIglS5bQokULgoOD8fb2to7OWrlyJR9++CEtWrRg3rx5ODo6puEORERERERE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4I6IiIiIiIiIiIiIiIiIpBolIuS5SUhI4Oeff+aDDz6gWLFixMTE8OabbxIZGcnEiROZNm0afn5+JCYm8sEHH7Bs2TLrsxkzZsTFxQWLxaLgjoj8Y8mDO7///jvXrl37R8/Z2NhQs2ZN1q5dS548eUhMTFRwR0RERERERERERERERERShVIR8kyYzebHPitcuDDHjh3j/fffJzExkb59+3L27Fn8/f1p0qQJAO+//z65c+fG3d2d1q1b880336R4x7/tniEir67kwZ1Nmzbx8ccfM3v2bBISEv7R87a2ttjb2wNgZ2f33NYpIiIiIiIiIiIiIiIiIpKcwjvyPzOZTNbuOIcOHWLjxo3WawUKFADg7t27bN++nXLlytGhQwecnZ0BMBqN3LlzhwYNGlC1alXKli2b+hsQkXQveXBn27ZtBAYGsmfPHurXr4/RaEQTIkVERERERERERERERETkRaXWAvI/MZvN1tEyo0ePZt68ecTExLBq1SqqVKliLaZHRETw22+/0aBBA5ycnKzPfvXVV7i7uzN27FgyZMiAvb09JpNJ42pE5B97NLjj5+fH8ePH2bdvH+XLlycqKorTp0/j6emJl5dXGq9WRERERERERERERERERCQldd6R/8xisVg77vTv359hw4ZRqlQpli1bRpUqVVLcmyVLFooUKcLcuXPZsmULCQkJfPXVV8yfPx9PT0+MRqN1XI2COyLyT/1VcGf79u1UqFCBqKgo5s2bR6NGjdi8eXMar1ZERERERERERERERERE5HEGi2aJyP9owYIFdO3alY4dO9KnTx/y5MnzxPumTZtGnz59MJlMZM2alVu3bpE7d2527NhB7ty5UxThRUSe5q+COzt27LAGd0JDQ/H396dQoULs378/jVcsIiIiIiIiIiIiIiIiIvI4hXfkX3lSwKZFixaEh4ezadMmihUr9tgzZrPZ2qFnyZIlbNu2jcuXL1O0aFH69etHzpw5NSpLRP6Vv+u4U7FiRWtwZ9CgQZQtW5adO3cCkJiYiJ2dJkaKiIiIiIiIiIiIiIiIyItD4R15qkOHDnHt2jXq1q2Lg4NDimuRkZEUKFCAqlWrsm7duhRBnb8SHx9vDerY2toquCMi/5mCOyIiIiIiIiIiIiIiIiKS3v19ykJeedevX6dx48a0aNGC48ePP3bdzs4Oo9HI77//zvXr1x8L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aDRSrlw58uXLh4uLi84eEREREREREREREREREUn3FN55hY0ZM4Zhw4bRuXNnOnToQIkSJTh48CBvvvkmMTExtGvXjgEDBlCkSBHrM7/++itr167FxcWFpk2bkiNHDhXPReSpLBYLBoPB+uvSpUvp1asXu3fvpmDBggBcv36d0NBQAgMDKVu2bIoAz9+9U0REREREREREREREREQkPVN45xW1d+9ePv30U0qVKsXw4cMpUKAAMTExvP7660RFRVGoUCF27txJu3bt6NevH8WKFXviexTcEZGnSX5OxMbGAg/PoIULF7Jw4UISEhIwGo0AREZGsnDhQgICAihbtiyjR4+matWqKYI/IiIiIiIiIiIiIiIiIiIvE7u0XoCkPovFwsGDB7l27RqzZ8+2BncqVarE7du3mThxIgULFmTAgAEsWLAAR0dHevTokaIDTxIFd0Tk75jNZus5MW7cONasWUNERARmsxkHBweio6PJlCmTdZyfm5sbbdu2BSAgIICAgACGDh1KzZo1FdwRERERERERERERERERkZeSwjuvIIPBQJMmTXBxcaFq1arExcXx8ccfc/nyZcaOHUvLli2xtbWlfv367Ny5kzlz5vDHH38wYcIE8uXLl9bLF5F0JGnk1eDBgxkzZgxeXl4YjUbu3r3LhQsXmDlzJj169MDZ2fmxAI+NjQ19+/bF1dWVqlWrYm9vn8a7ERERERERERERERERERF59jQ26yWXfMxM0teJiYnY2f2Z29q3bx/vvPMO77zzDnPmzLEW27ds2UL37t0pXrw4Bw4c4MiRI7i5uaXJPkQkfUk+Kuv8+fPUrFmTt99+m4EDB5IhQwY2bNhAYGAgDx48YMSIEbRp0wZHR0drgAfgxo0brFmzhgYNGuDl5ZWW2xEREREREREREREREREReW7Ueecllrx4npiYSHx8PM7OztbgTlKY5+jRo0RGRlK3bl1r0Rxg9erVZMiQgTFjxpA9e3YyZ86corAuIvJXks6e77//nqioKAC6du2Kt7c3AC1btiRHjhx07tyZwMBALBYLH374YYoAT/bs2enYsSMGgyHFeSYiIiIiIiIiIiIiIiIi8jJRCuMlZTabrYXusWPHUq9ePUqXLs3IkSM5cuQIgLUjT4ECBbC1tWXv3r3Ex8cDsGzZMjZv3kyFChXImzcvmTNnxmKxKLgjIv/YrFmz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FAqpq6vLMccck1//+tc57LDDcsstt6R169YrbKH1z/cEWJlisdgc7owfPz433XRT5syZk759++bLX/5yvvCFL6Rt27ZJ3pm8s88++6RTp065+eab07lz5/ziF7/IBRdckG233TZTpkxpnsYDAAAAAAAAwP8R76zl3v3l+ahRo3LllVfmjTfeaD6+7bbbZuLEidl3332z2Wab5bXXXssxxxyTe++9d6UBD8AHdc455+S73/1uttlmm7Rv3z5z585NQ0NDTjvttIwcOTIdOnTIkiVLMn78+Fx88cXp1q1bWrduneeeey6dO3dOTU1NevTokVKplEKh0NKvAwAAAAAAALBWsW3WWm5ZuDN27NhMnDgxxxxzTB544IEsXrw43//+91NXV5djjz02999/fxobG9OmTZvceuutGTBgQO66664MGDAgb731lnAHeN/eva3VnDlzcvPNN2fYsGH57W9/m+nTp+fuu+/O9ttvnwkTJmT8+PFZtGhR2rZtm9NPPz2XXHJJunfvnurq6gwcODAPPPBAevTokaamJuEOAAAAAAAAwEqYvLMOeOSRRzJw4MDsvffemThxYnr37p0kuemmm3LqqaemVatWmTlzZtq3b988Yee1117LwQcfnFmzZuWpp55Kp06dWvgtgHXNtGnTMm/evJx11ln51a9+lZ133rn52LPPPpujjz46jzzySEaMGJGzzjorm222WZb9k9LY2JhCoZCKigqTvwAAAAAAAAD+DZN31gHz5s3Liy++mK985Svp3bt3mpqacvPNN2f06NFp3759HnvssbRv3z719fXNX5C3adMmv/71r5vDnWKx2MJvAaxLJk2alL59+2bKlCnZZZddsvPOO6epqak5zunRo0duv/32fOITn8jFF1+ciRMn5tVXX22erlNZWZmKiookEe4AAAAAAAAA/BvinXVAbW1tkuTjH/94kuS2227LqFGjUigU8sc//jGbbbZZkuTpp5/OUUcd1RzqbLLJJs3hzrLttwDej7322it77LFHfvazn+Xhhx9ObW3tChFO9+7dmwOeyy+/POedd17q6upsjwUAAAAAAADwASg61gGbbrppkmTKlCm59dZbM2rUqJSVlWXGjBnLbYd10UUX5Te/+U3mzp273PXCHeCD2nPPPXP11VfnM5/5TBYvXpxJkybllVdeSaFQyLt3W+zevXumTJmS7t275+67727BFQMAAAAAAACsmwqld38LS4splUorTKtY9tmCBQuy5557prGxMYVCIZWVlXnsscfSrl275nN/9KMf5fzzz8/hhx+eSy+9NNXV1Wv6FYD1TKlUyqOPPppTTjkljz/+eM4///wMGTIkm2666Qq/s+bPn5+ysrJ06dJlpb/PAAAAAAAAAFg5I1la0LLtrZqampq/6K6rq8sLL7yQJM2fbbbZZjn77LPT2NiY+fPnZ8KECcuFO9dff33Gjx+f9u3b5/zzz091dXU0WcBHVSgUsvvuu+eqq67KjjvumHHjxuXqq6/Oq6++usIEnm7duqVLly4pFovCHQAAAAAAAIAPwOSdFvDMM8+kc+fOadWqVRoaGlJZWZkk+c53vpNbbrklTz75ZA455JB8/vOfz1e/+tWUl5dn3rx5ueaaazJp0qR07tw5++23X/bee+/cd999uf/++9O2bdvU1NSkZ8+eaWpqSnl5eQu/JbA+efTRRzN06NDMmjUr5557bk466aR06NChpZcFAAAAAAAAsM4T76xhM2bMSP/+/XPGGWfk3HPPTevWrZMko0ePzoQJE7Ltttumffv2mT17dt58881885vfzIUXXpiKioo8//zzqampyYQJE/K3v/0tSdKjR4/0798/48aNyxZbbCHcAVabZQHPnDlzcuqpp2b48OFp3759Sy8LAAAAAAAAYJ0m3lnDZs2alQMOOCCvv/56Ro4cmeHDh2fhwoXp169fPv/5z+fss89Ot27d8vjjj+foo49ObW1thg8fngkTJqSioiJJUl9fn7lz52bJkiXZcccdU1VVlY022ki4A6x2f/nLX3LUUUeloqIiDz/8cNq0adPSSwIAAAAAAABYp4l3WsCsWbNyxBFHpLa2NmPGjMn222+fESNG5K677sqOO+6YUqmUQqGQ+fPnZ7/99su8efNWCHj+2bJrAFa3J554Ip06dUqXLl387gEAAAAAAAD4iMQ7a9iyL7pnzZqVL3zhC3nxxRfzqU99KvX19ampqUlDQ0MqKyvT2NiYioqKLFiwIJ/61Kcyb968jBgxIuPHj09FRUWKxWLKyspa+nWADZjfQwAAAAAAAAAfnXhnNXr3RIpisZhSqbTctlZ/+9vfMnDgwMyZMyc9evTIX//617Rt27b5C/GVBTwnnXRSrrzySttjAQAAAAAAAACsB4xMWE3+eSuZsrKy5uDmRz/6UR577LHsuOOO+fnPf55dd901zz77bM4///y8/vrrKSsrS7FYTEVFRRobG9OlS5dMnz49bdq0yZQpU/Laa6+11GsBAAAAAAAAALAKiXdWk2Xhzt57753DDz+8+fNhw4bl61//ep544ok0NDSkT58+uemmm7LDDjvkhz/8YS699NK89dZbKw145syZk8ceeyzt27ePgUkAAAAAAAAAAOu+ipZewPrs+eefz9tvv5277rorX//617Pxxhvnhz/8Yb7xjW9k//33T2VlZZKkT58+uf3223PEEUfkO9/5TpJk5MiRadWqVXPA09TUlE6dOiVJmpqabJsFAAAAAAAAALAeKJSMcFmtamtrc+qpp+aee+5Jkpx77rkZPnx4OnTosMK5Tz75ZI444og888wzGT16dHPA889bcAEAAAAAAAAAsH6wbdZqVCwWs9VWWy03Jefpp59uDncaGhqWO3+HHXbInXfema222irf+973csEFF+Ttt98W7gAAAAAAAAAArKfEO6vRsuhmiy22yFFHHZV99903P/3pT3PCCSckSSorK9PU1LTcNcsCnqqqqvz0pz/N0qVL1/i6AQAAAAAAAABYM2ybtRo1NDSksrKy+c/PPfdcTjjhhPzud7/L8ccfnx/96EdJkqVLl6aqqipJ8tprr6VNmzaZPXt2Ntlkk3Tr1s22WQAAAAAAAAAA6ynxzipULBZTVvZ/w4xWFt3MmjUrp512Wn77298uF/Akyb333pv77rsvX/3qV/Oxj30sSdLU1LTctlsAAAAAAAAAAKw/xDuryLsjmzvuuCMPP/xw/vKXv+Tzn/98PvWpT2XXXXdtPvepp57Kqaee2hzwXHLJJfnVr36VsWPH5u23386MGTPSuXPnFnoTAAAAAAAAAADWFPHOKvDuiTtnn312rrjiipSVlWWTTTbJwoULs/POO+fMM8/Mcccd13zN008/nW9+85u5995707Zt29TX16dz586ZOnVqevXqtcIUHwAAAAAAAAAA1j/inVVozJgxGTduXI499ticfPLJ+eQnP5lrr702Q4cOTc+ePfOtb30rgwcPbj5/3rx5mTx5ch5//PG0a9cuF1xwQbbYYos0NjamoqKi5V4EAAAAAAAAAIA1QrzzAZRKpRQKhRV+TpKf/exnOfXUU3PooYdmxIgR2W677VJXV5d+/frl2WefTV1dXTp27JiLLrooxx9/fPN1y7bbWrp0aaqqqpbbfgsAAAAAAAAAgPWbfZnep2KxuFysUygUUiwWkyT19fX53e9+l8bGxgwdOjTbbbddXn/99Xzyk5/MwoULM3ny5Pz3f/93Fi1alNGjR+faa69tvs+ydqqqqipJhDsAAAAAAAAAABsQ8c77UCqVUlb2zl/Vl7/85Vx00UVJkrKysuaoZ+utt86ll16a3XffPW+//XYOP/zwvPLKKxk3blwOOuignHDCCTn44IPz0ksvZfz48bnqqquSxPZYAAAAAAAAAAAbMOXI+7Bs4s4+++yTGTNm5MEHH0ybNm0ydOjQlJWVpaqqKkOGDGmemnPDDTdk2rRpGTFiRP7f//t/zVN1evfunV69emXu3LmZNGlSBg8enNatW7fYewEAAAAAAAAA0LLEO+9TbW1tXnzxxVRVVeW5557Leeedl7KysgwZMiRJsvHGGzef+8QTT6SsrCxnnnnmcnHOzJkzc+KJJ2b//fdPly5dhDsAAAAAAAAAABs422a9T126dEm/fv2y5ZZb5vTTT8+iRYsycuTIXHvttc3nNDQ0pKGhIQsWLEh9fX3uv//+5mO33nprZs+enerq6uy1117p2bNnGhsbW+JVAAAAAAAAAABYSxRKpVKppRextiuVSikUCnnsscey++67Z8KECdl1111z8MEHp23btrnoooty4oknNp9/33335eCDD84uu+ySQYMG5eWXX85tt92W6urqPPDAA+natWsLvg0AAAAAAAAAAGsL8c6/sSzaSZJisZiGhoZ86UtfyvTp0zN9+vTMmDEjgwYNSrt27fK9732vOeBpaGjItddem+HDh6e+vj7V1dXZZZddctttt6Vnz54pFospKzP0CAAAAAAAAABgQ1fR0gtYm/xzVLMs3CmVSikrK0t1dXWOPPLI3HnnnampqckJJ5yQ+vr6DB48OGeeeWaS5MQTT0xlZWWGDRuWvn375rHHHkvHjh2z1157ZdNNN01TU1PKy8tb5P0AAAAAAAAAAFi7mLzzv94d7gwePDg77bRTjjvuuHTq1Cnl5eVpbGxMRcU7rVP//v0zf/78zJgxI+3atcuNN96YL3/5y2nXrl0mTpyYIUOGvOczAAAAAAAAAABASfK/lkU1++yzT37yk5/krLPOyqGHHpphw4blhRdeSGNjY5J3ApzD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AAAAAAAAa4NC6d2jbdYixWIxZWXvDAb6/ve/n6lTp6ZYLGb//ffPsGHDstFGGyVJpk6dmsGDB+e5557LKaeckn79+mXbbbfNddddlzvvvDOlUinTp09Pjx49lrsnAAAAAAAAAAC0tLU23llm1KhRmThxYtq0aZP6+vosXbo0RxxxREaNGpU99tgjZWVlefDBBzN27Ng8+OCDWbp0aZJko402St++fXPNNdekR48ey03xAQAAAAAAAACAtcFaHe/84Q9/yNFHH53DDz88Z5xxRurr63P99dfnqquuyh577JFx48Zl3333TVlZWebPn5/58+dnxowZqayszB577JHevXunTZs2wh0AAAAAAAAAANZKa1W8UyqVUigUmv9/yy235PTTT88DDzyQ7bbbLkmycOHCTJ48OWPGjMnuu+++XMDz7+4JAAAAAAAAAABrm5UXLy2gqampObJ5++2389Zbb6Vz584ZMGBAtttuuzQ0NCRJOnfunMGDB+fb3/52/vznP+fcc8/N9OnTs6xB+ucWSbgDAAAAAAAAAMDaaq2YvFMsFpsn53zve9/LnXfemQULFqRYLKa6ujozZsxI27Ztlztv0aJF+fGPf5xvfetb2WuvvXL++eenf//+Yh0AAAAAAAAAANYZa8XknWVBzujRo3P22WfnhRdeSKFQyGuvvZann346P/jBD/Lmm2+mrKwsxWIxSdKxY8ccf/zxufDCC1NTU5MrrriieToPAAAAAAAAAACsC1p08k5TU1PKy8uTJLW1tenfv38+97nP5eyzz87GG2+ce+65J2PGjMnbb7+d//zP/8yxxx6bjTbaaLkJPC+//HLuvPPOHHLIIenevXtLvQoAAAAAAAAAAHxga8W2Wb/85S9TV1eXUaNG5a677spOO+2UJFm6dGlqamoydOjQNDQ0ZOzYsTnuuONWCHhKpVIKhcJyMRAAAAAAAAAAAKztWjze+a//+q98/etfz2c+85ksXbo0v//979PQ0JCKiormIGfq1KkZMmRIGhoacsEFF6x0Ag8AAAAAAAAAAKxrWjzeeeGFF/LFL34xDz30UNq3b59HH300PXv2XC7MeXfAUyqVMnLkyAwZMiTV1dUtuXQAAAAAAAAAAPhIWnxszRZbbJEpU6bkoIMOyj/+8Y+ce+65WbBgQcrKylIsFpMk5eXl2X///XPttddm0aJFuf7669PY2NjCKwcAAAAAAAAAgI9mjU3eKZVKKRQKK3ze1NSU8vLyvPTSSxk0aFDuv//+nH766Rk9enQ6deq0wgSehx56KL169crmm2++JpYNAAAAAAAAAACrzRqJd5YFOkny0ksvZcmSJWlsbMyOO+643HkvvfRSjjnmmDzwwAP5xje+kXPPPXeFgGdl9wQAAAAAAAAAgHXRat82692RzUUXXZRDDjkku+yyS/bee+988YtfzOOPP968BVbXrl1z6623pm/fvrn88sszbty4vPzyyykrK8s/N0bCHQAAAAAAAAAA1nWrdfLOu7fKGjlyZC699NLstNNOOeSQQ7Jw4cLcfvvt2XXXXXPOOefkgAMOSFVVVZL/m8Azffr0fOUrX8nEiRPTsWPH1bVMAAAAAAAAAABoEWtk26zLLrss5513Xk488cQMGTIkH/vYx/Lss8/mk5/8ZObPn59dd90148aNy2c/+9lUVlYmSRYsWJDPfvazefXVVzNz5sy0b99+dS8TAAAAAAAAAADWqNUe78ycOTODBw9Ot27dMnHixPTp0ydLlizJ3nvvnbq6uhx66KG5/fbb07t373z729/OgQce2DyB5+WXX06xWEyXLl2Wm+IDAAAAAAAAAADrg4rV/YDa2to8/fTTmTBhQvr06ZM33ngj/fr1y6uvvprLLrssn/70p7PxxhvniiuuyHe/+92UlZU1b6HVqVOnJEmxWExZWdnqXioAAAAAAAAAAKxRa2TbrHvvvTcDBgxIQ0NDhgwZkjvuuCPjx4/PiSeemOrq6kydOjUHHHBAqqur06FDh9x4443Zf//9V/eyAAAAAAAAAACgRa2ycTbFYnGFz5YuXZokGTBgQJJkwYIFqampSf/+/XPKKaekuro6SbL55punW7duOe2009K1a9f06dNnVS0LAAAAAAAAAADWWqsk3mlqamre1mrx4sV5/vnnkyRVVVVJkmXDfWbNmpVnn302u+2223LX//SnP02bNm1yyimnZNq0aenWrVuamppWxdIAAAAAAAAAAGCt9ZHjnWKxmPLy8iTJ+PHj069fv2y77bY56KCDMnny5CxZsiSFQiFJsvPOO6dnz5755S9/mblz5+a1117LTTfdlMmTJ2ebbbZJ165d06pVqyRpvicAAAAAAAAAAKyvCqVlY3E+onPOOSff/e53s80226R9+/aZO3duGhoactppp2XkyJHp0KFDlixZkvHjx+fiiy9Ot27d0rp16zz33HPp3Llzampq0qNHj5RKpebYBwAAAAAAAAAA1mcfevLOu7e1mjNnTm6++eYMGzYsv/3tbzN9+vTcfffd2X777TNhwoSMHz8+ixYtStu2bXP66afnkksuSffu3VNdXZ2BAwfmgQceSI8ePdLU1CTcAQAAAAAAAABgg/GRJ+9MmzYt8+bNy1lnnZVf/epX2XnnnZuPPfvsszn66KPzyCOPZMSIETnrrLOy2WabZdkjGxsbUygUUlFRkaamJltlAQAAAAAAAACwQfnQk3eSZNKkSenbt2+mTJmSXXbZJTvvvHOampqa45wePXrk9ttvzyc+8YlcfPHFmThxYl599dXm6TqVlZWpqKhIEuEOAAAAAAAAAAAbnI8U7+y1117ZY4898rOf/SwPP/xwamtrV4hwunfv3hzwXH755TnvvPNSV1dneywAAAAAAAAAADZ4Hyne2XPPPXP11VfnM5/5TBYvXpxJkybllVdeSaFQyLt34+revXumTJmS7t275+677/7IiwYAAAAAAAAAgPVBofTuyuZDKJVKefTRR3PKKafk8ccfz/nnn58hQ4Zk0003TalUWm7Czvz581NWVpYuXbqscAwAAAAAAAAAADY0HzneWebRRx/N0KFDM2vWrIwePTonnXTSSgOeJCkWiykr+0hDfwAAAAAAAAAAYJ23ygqa3XbbLT/84Q/Tp0+ffOc738k111yTxYsXr3S6jnAHAAAAAAAAAABW4eSdZZZN4JkzZ05OPfXUDB8+PO3bt1+VjwAAAAAAAAAAgPXCKo93kuQvf/lLjjrqqFRUVOThhx9OmzZtVvUjAAAAAAAAAABgnbda4p0keeKJJ9KpU6d06dIlpVJppdtnAQAAAAAAAADAhmy1xTvLFIvFlJWVrc5HAAAAAAAAAADAOmm1xzsAAAAAAAAAAMDKGYkDAAAAAAAAAAAtRLwDAAAAAAAAAAAtRLwDAAAAAAAAAAAtRLwDAAAAAAAAAAAtRLwDAAAAAAAAAAAtRLwDAAAAAAAAAAAtRLwDAAAAAAAAAAAtRLwDAAAA8AHdd999OeGEE7Lddtulbdu2qa6uTrdu3XLggQfm0ksvzcsvv/yB7ldbW5tCoZCtttpq9Sz4Q6qpqUmhUEj//v1beikAAAAA6y3xDgAAAMD79Morr+TAAw/MQQcdlOuvvz4NDQ3Zf//9c+SRR2aHHXbI9OnTM3z48PTq1St//OMfV8kzt9pqqxQKhdTW1q6S+wEAAACwdqlo6QUAAAAArAvq6uqy33775amnnkqfPn1y9dVXp2/fvsudU19fnx//+McZM2ZM5s+f/77vvcUWW+TJJ59MZWXlql72R7LXXnvlySefTOvWrVt6KQAAAADrrUKpVCq19CIAAAAA1nZf+cpXMnny5Gy11Vb505/+lE033fRfnrtgwYL84x//yPbbb/+Rn7vVVlvlmWeeybx589a6bbUAAAAA+OhsmwUAAADwHv7+97/npptuSpJccskl/zbcSZIuXbo0hztjx45NoVDI2LFj8+yzz+ZrX/taunfvnsrKygwePDhJUltbm0KhsFycc/3116dQKOSZZ55Jkmy99dYpFArN/9XU1Cz3zBdffDHDhw/PDjvskNatW6dNmzbZc889M2nSpDQ2Nq6wxsGDB6dQKOT666/PE088kWOOOSbdunVLeXl5xo4dmySpqalJoVBI//79V7j+N7/5TU477bTsuuuu2WyzzVJdXZ0tt9wyxxxzTB5++OH38bcKAAAAQGLbLAAAAID3dNddd6WpqSnt27fP4Ycf/qHuMXv27Oy2226pqqrKvvvum1KplM022+xfnr/NNtvk+OOPz+2335433ngjRx55ZDbZZJPm4127dm3++fe//30GDhyYxYsXZ6uttsqBBx6Y+vr6zJgxI6eddlp+8Ytf5K677lrptlzTp0/PySefnG7duqVfv35566230qZNm/d8n5NPPjnPPfdcPvaxj2XfffdNRUVFZs2aldtuuy133HFHbrnllhx55JEf8G8JAAAAYMMj3gEAAAB4D4888kiSZPfdd095efmHusdNN92U4447Ltdee22qq6vf8/z99tsv++23X2pqavLGG2/koosuWum2WS+99FL+4z/+I//4xz9y1VVXZejQoSkre2fY8qJFi/LFL34xv/71rzN+/Ph861vfWuH6a665JqNGjcq4ceOar3s/Lrroonz6059Ohw4dlvv8f/7nf3L00Udn6NChOfTQQ9OqVav3fU8AAACADZFtswAAAADew8svv5wk6dy584e+x6abbppJkya9r3Dng7jsssuyaNGinHLKKRk2bNhyAU7Hjh3zk5/8JJWVlZk0aVJKpdIK12+33Xa58MILP1C4kyQDBw5cIdxZ9vnRRx+dRYsWZerUqR/8hQAAAAA2MCbvAAAAAKwBn/3sZ9OuXbtVft+77747SXLMMces9PgWW2yRbbfdNn/7298ye/bsbLfddssdHzhw4IeeJvTiiy/m7rvvzqxZs1JXV5fGxsYkycyZM5MkTz31VA499NAPdW8AAACADYV4BwAAAOA9dOrUKUmycOHCD32PlW15tSr8/e9/T5L07dv3Pc99+eWXV4h3Puy6LrjggowbNy4NDQ3/8pwlS5Z8qHsDAAAAbEjEOwAAAADvYY899sjkyZPz5z//OU1NTR9qUk2rVq1Ww8qSYrGYJDnqqKOy8cYb/9tzO3bsuErWdccdd2Ts2LHZZJNNMmnSpHzmM5/J5ptvnlatWqVQKGT06NEZP378SrfpAgAAAGB54h0AAACA93DYYYdl+PDh+cc//pGf//znOeKII1p6Sc26d++e2bNn5+yzz84nPvGJNfLM2267LUkybty4nHTSSSscnz179hpZBwAAAMD6oKylFwAAAACwtuvdu3cGDRqUJBkxYkReffXVf3v+woUL89RTT62SZ1dVVSVJGhsbV3r8kEMOSfJ/Qc2asOz9e/bsucKxhQsX5r777ltjawEAAABY14l3AAAAAN6HK664Ittss03mzZuX/fbbLw8++OAK5yxdujTXXXdddttttzz55JOr5LlbbrllkmTmzJkrPX7mmWemffv2ueSSS3LxxRdn6dKlK5wzb9683HDDDatkPUmyww47JEmuvvrq5Z5XV1eX448/PnV1davsWQAAAADrO9tmAQAAALwPHTp0yLRp03LMMcekpqYmffv2zdZbb51ddtklrVu3zoIFCzJjxoy8/vrradu2bTbffPNV8twjjzwyU6dOzXHHHZeDDjooHTp0SPJOtLP99ttnyy23zM9+9rMceeSRGTlyZCZOnJiddtop3bp1S11dXZ588snMnTs3e++9d4477rhVsqZvfvOb+clPfpJ77rknvXr1yj777JOGhobcf//9ad26db761a/muuuuWyXPAgAAAFjfiXcAAAAA3qfOnTtn6tSpuffee3PzzTdn+vTp+e1vf5v6+vp07Ngxn/zkJ/O5z30uX/7yl7PpppuukmcOGzYsr732Wm644Ybcc889efvtt5Mkxx13XLbffvskSb9+/TJz5sxMmjQpd999dx5++OHU19enc+fO6dGjR4477rgceeSRq2Q9SbL11lvn0UcfzXnnnZcHHnggd911V7p27ZpBgwZl7Nix+cEPfrDKngUAAACwviuUSqVSSy8CAAAAAAAAAAA2RGUtvQAAAAAAAAAAANhQiXcAAAAAAAAAAKCFiHcAAAAAAAAAAKCFiHcAAAAAAAAAAKCFiHcAAAAAAAAAAKCFiHcAAAAAAAAAAKCFiHcAAAAAAAAAAKCFiHcAAAAAAAAAAKCFiHcAAAAAAAAAAKCFiHcAAAAAAAAAAKCFiHcAAAAAAAAAAKCFiHcAAAAAAAAAAKCFiHcAAAAAAAAAAKCF/H9N274RyqxjZwAAAABJRU5ErkJggg==", "text/plain": [ "
" ] @@ -3597,7 +2784,7 @@ "plt.title(\n", " \"Average Values of 3 different baselines cases with 95% Confidence Intervals - math problems \", fontsize=12, pad=10\n", ") # Adjust titlepad to move the title further above\n", - "plt.xticks(index + bar_width / 2, criteria, rotation=45, fontsize=14)\n", + "plt.xticks(index + bar_width / 2, [crit.name for crit in criteria], rotation=45, fontsize=14)\n", "plt.legend(loc=\"upper center\", fontsize=14, bbox_to_anchor=(0.5, 1), ncol=3) # Adjust legend placement and ncol\n", "plt.tight_layout() # Adjust subplot parameters to fit the labels\n", "plt.ylim(0, 5)\n", diff --git a/notebook/gpt_assistant_agent_function_call.ipynb b/notebook/gpt_assistant_agent_function_call.ipynb new file mode 100644 index 00000000000..6febb89cc9b --- /dev/null +++ b/notebook/gpt_assistant_agent_function_call.ipynb @@ -0,0 +1,566 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "hLnLsw8SaMa0" + }, + "source": [ + "# From Dad Jokes To Sad Jokes: Function Calling with GPTAssistantAgent\n", + "\n", + "Autogen allows `GPTAssistantAgent` to be augmented with \"tools\" β€” pre-defined functions or capabilities β€” that extend its ability to handle specific tasks, similar to how one might natively utilize tools in the [OpenAI Assistant's API](https://platform.openai.com/docs/assistants/tools).\n", + "\n", + "In this notebook, we create a basic Multi-Agent System using Autogen's `GPTAssistantAgent` to convert Dad jokes on a specific topic into Sad jokes. It consists of a \"Dad\" agent which has the ability to search the [Dad Joke API](https://icanhazdadjoke.com/api) and a \"Sad Joker\" agent which converts the Dad jokes into Sad jokes. The Sad Joker then writes the sad jokes into a txt file.\n", + "\n", + "In this process we demonstrate how to call tools and perform function calling for `GPTAssistantAgent`." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "9E3_0867da8p" + }, + "source": [ + "## Requirements\n", + "AutoGen requires Python 3.8 or newer. For this notebook, please install `pyautogen`:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "id": "pWFw6-8lMleD" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Requirement already satisfied: pyautogen in /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages (0.2.8)\n", + "Requirement already satisfied: openai>=1.3 in /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages (from pyautogen) (1.6.1)\n", + "Requirement already satisfied: diskcache in /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages (from pyautogen) (5.6.3)\n", + "Requirement already satisfied: termcolor in /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages (from pyautogen) (2.4.0)\n", + "Requirement already satisfied: flaml in /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages (from pyautogen) (2.1.1)\n", + "Requirement already satisfied: python-dotenv in /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages (from pyautogen) (1.0.0)\n", + "Requirement already satisfied: tiktoken in /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages (from pyautogen) (0.5.2)\n", + "Requirement already satisfied: pydantic<3,>=1.10 in /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages (from pyautogen) (2.5.3)\n", + "Requirement already satisfied: docker in /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages (from pyautogen) (7.0.0)\n", + "Requirement already satisfied: anyio<5,>=3.5.0 in /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages (from openai>=1.3->pyautogen) (4.2.0)\n", + "Requirement already satisfied: distro<2,>=1.7.0 in /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages (from openai>=1.3->pyautogen) (1.8.0)\n", + "Requirement already satisfied: httpx<1,>=0.23.0 in /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages (from openai>=1.3->pyautogen) (0.26.0)\n", + "Requirement already satisfied: sniffio in /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages (from openai>=1.3->pyautogen) (1.3.0)\n", + "Requirement already satisfied: tqdm>4 in /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages (from openai>=1.3->pyautogen) (4.66.1)\n", + "Requirement already satisfied: typing-extensions<5,>=4.7 in /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages (from openai>=1.3->pyautogen) (4.9.0)\n", + "Requirement already satisfied: annotated-types>=0.4.0 in /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages (from pydantic<3,>=1.10->pyautogen) (0.6.0)\n", + "Requirement already satisfied: pydantic-core==2.14.6 in /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages (from pydantic<3,>=1.10->pyautogen) (2.14.6)\n", + "Requirement already satisfied: packaging>=14.0 in /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages (from docker->pyautogen) (23.2)\n", + "Requirement already satisfied: requests>=2.26.0 in /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages (from docker->pyautogen) (2.31.0)\n", + "Requirement already satisfied: urllib3>=1.26.0 in /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages (from docker->pyautogen) (2.1.0)\n", + "Requirement already satisfied: NumPy>=1.17.0rc1 in /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages (from flaml->pyautogen) (1.26.2)\n", + "Requirement already satisfied: regex>=2022.1.18 in /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages (from tiktoken->pyautogen) (2023.10.3)\n", + "Requirement already satisfied: idna>=2.8 in /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages (from anyio<5,>=3.5.0->openai>=1.3->pyautogen) (3.6)\n", + "Requirement already satisfied: certifi in /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages (from httpx<1,>=0.23.0->openai>=1.3->pyautogen) (2023.11.17)\n", + "Requirement already satisfied: httpcore==1.* in /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages (from httpx<1,>=0.23.0->openai>=1.3->pyautogen) (1.0.2)\n", + "Requirement already satisfied: h11<0.15,>=0.13 in /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages (from httpcore==1.*->httpx<1,>=0.23.0->openai>=1.3->pyautogen) (0.14.0)\n", + "Requirement already satisfied: charset-normalizer<4,>=2 in /Users/justintrugman/.pyenv/versions/3.11.7/lib/python3.11/site-packages (from requests>=2.26.0->docker->pyautogen) (3.3.2)\n", + "\n", + "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m23.3.2\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m24.0\u001b[0m\n", + "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n", + "Note: you may need to restart the kernel to use updated packages.\n" + ] + } + ], + "source": [ + "pip install pyautogen" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "jnH9U6MIdwUl" + }, + "source": [ + "Import Dependencies" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "id": "Ga-yZeoBMzHs" + }, + "outputs": [], + "source": [ + "from typing import Annotated, Literal\n", + "\n", + "import requests\n", + "\n", + "import autogen\n", + "from autogen import UserProxyAgent\n", + "from autogen.agentchat.contrib.gpt_assistant_agent import GPTAssistantAgent\n", + "from autogen.function_utils import get_function_schema\n", + "\n", + "config_list = autogen.config_list_from_json(\n", + " env_or_file=\"OAI_CONFIG_LIST\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "02lZOEAQd1qi" + }, + "source": [ + "## Creating the Functions\n", + "We need to create functions for our Agents to call.\n", + "\n", + "This function calls the Dad Joke API with a search term that the agent creates and returns a list of dad jokes." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "id": "jcti0u08NJ2g" + }, + "outputs": [], + "source": [ + "def get_dad_jokes(search_term: str, page: int = 1, limit: int = 10) -> str:\n", + " \"\"\"\n", + " Fetches a list of dad jokes based on a search term.\n", + "\n", + " Parameters:\n", + " - search_term: The search term to find jokes about.\n", + " - page: The page number of results to fetch (default is 1).\n", + " - limit: The number of results to return per page (default is 20, max is 30).\n", + "\n", + " Returns:\n", + " A list of dad jokes.\n", + " \"\"\"\n", + " url = \"https://icanhazdadjoke.com/search\"\n", + " headers = {\"Accept\": \"application/json\"}\n", + " params = {\"term\": search_term, \"page\": page, \"limit\": limit}\n", + "\n", + " response = requests.get(url, headers=headers, params=params)\n", + "\n", + " if response.status_code == 200:\n", + " data = response.json()\n", + " jokes = [joke[\"joke\"] for joke in data[\"results\"]]\n", + " return jokes\n", + " else:\n", + " return f\"Failed to fetch jokes, status code: {response.status_code}\"" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "id": "2FgsfBK1NsPj" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['Where do cats write notes?\\r\\nScratch Paper!', 'It was raining cats and dogs the other day. I almost stepped in a poodle.', 'What do you call a group of disorganized cats? A cat-tastrophe.', 'I accidentally took my cats meds last night. Don’t ask meow.', 'What do you call a pile of cats? A Meowtain.', 'Animal Fact #25: Most bobcats are not named bob.']\n" + ] + } + ], + "source": [ + "# Example Dad Jokes Function Usage:\n", + "jokes = get_dad_jokes(\"cats\")\n", + "print(jokes)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "DC9D5bKEeoKP" + }, + "source": [ + "This function allows the Agents to write to a txt file." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "id": "wXAA2MtoOS_w" + }, + "outputs": [], + "source": [ + "def write_to_txt(content: str, filename: str = \"dad_jokes.txt\"):\n", + " \"\"\"\n", + " Writes a formatted string to a text file.\n", + " Parameters:\n", + "\n", + " - content: The formatted string to write.\n", + " - filename: The name of the file to write to. Defaults to \"output.txt\".\n", + " \"\"\"\n", + " with open(filename, \"w\") as file:\n", + " file.write(content)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "id": "xAgcFXEHOfcl" + }, + "outputs": [], + "source": [ + "# Example Write to TXT Function Usage:\n", + "content = \"\\n\".join(jokes) # Format the jokes from the above example\n", + "write_to_txt(content)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create Function Schemas\n", + "In order to use the functions within our GPTAssistantAgents, we need to generate function schemas. This can be done by using `get_function_schema`" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "# Assistant API Tool Schema for get_dad_jokes\n", + "get_dad_jokes_schema = get_function_schema(\n", + " get_dad_jokes,\n", + " name=\"get_dad_jokes\",\n", + " description=\"Fetches a list of dad jokes based on a search term. Allows pagination with page and limit parameters.\",\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "The return type of the function 'write_to_txt' is not annotated. Although annotating it is optional, the function should return either a string, a subclass of 'pydantic.BaseModel'.\n" + ] + } + ], + "source": [ + "# Assistant API Tool Schema for write_to_txt\n", + "write_to_txt_schema = get_function_schema(\n", + " write_to_txt,\n", + " name=\"write_to_txt\",\n", + " description=\"Writes a formatted string to a text file. If the file does not exist, it will be created. If the file does exist, it will be overwritten.\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "sgpx2JQme2kv" + }, + "source": [ + "## Creating the Agents\n", + "In this section we create and configure our Dad and Sad Joker Agents" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "6X40-Sk6Pcs8" + }, + "source": [ + "### Set up the User Proxy" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "id": "mEpxEaPdPSDp" + }, + "outputs": [], + "source": [ + "user_proxy = UserProxyAgent(\n", + " name=\"user_proxy\",\n", + " is_termination_msg=lambda msg: \"TERMINATE\" in msg[\"content\"],\n", + " human_input_mode=\"NEVER\",\n", + " max_consecutive_auto_reply=1,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "q4ym9KlMPenf" + }, + "source": [ + "### The Dad Agent\n", + "We create the Dad agent using `GPTAssistantAgent`, in order for us to enable the Dad to use the `get_dad_jokes` function we need to provide it the function's specification in our `llm_config`.\n", + "\n", + "We format the `tools` within our `llm_config` in the same format as provided in the [OpenAI Assistant tools docs](https://platform.openai.com/docs/assistants/tools/function-calling)." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "id": "kz0c_tVIPgi6" + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "OpenAI client config of GPTAssistantAgent(the_dad) - model: gpt-4-1106-preview\n", + "Matching assistant found, using the first matching assistant: {'id': 'asst_BLBUwYPugb1UR2jQMGAA7RtU', 'created_at': 1714660644, 'description': None, 'file_ids': [], 'instructions': \"\\n As 'The Dad', your primary role is to entertain by fetching dad jokes which the sad joker will transform into 'sad jokes' based on a given theme. When provided with a theme, such as 'plants' or 'animals', your task is as follows:\\n\\n 1. Use the 'get_dad_jokes' function to search for dad jokes related to the provided theme by providing a search term related to the theme. Fetch a list of jokes that are relevant to the theme.\\n 2. Present these jokes to the sad joker in a format that is clear and easy to read, preparing them for transformation.\\n\\n Remember, the team's goal is to creatively adapt the essence of each dad joke to fit the 'sad joke' format, all while staying true to the theme provided by the user.\\n \", 'metadata': {}, 'model': 'gpt-4-1106-preview', 'name': 'the_dad', 'object': 'assistant', 'tools': [ToolFunction(function=FunctionDefinition(name='get_dad_jokes', description='Fetches a list of dad jokes based on a search term. Allows pagination with page and limit parameters.', parameters={'type': 'object', 'properties': {'search_term': {'type': 'string', 'description': 'search_term'}, 'page': {'type': 'integer', 'default': 1, 'description': 'page'}, 'limit': {'type': 'integer', 'default': 10, 'description': 'limit'}}, 'required': ['search_term']}), type='function')]}\n" + ] + } + ], + "source": [ + "the_dad = GPTAssistantAgent(\n", + " name=\"the_dad\",\n", + " instructions=\"\"\"\n", + " As 'The Dad', your primary role is to entertain by fetching dad jokes which the sad joker will transform into 'sad jokes' based on a given theme. When provided with a theme, such as 'plants' or 'animals', your task is as follows:\n", + "\n", + " 1. Use the 'get_dad_jokes' function to search for dad jokes related to the provided theme by providing a search term related to the theme. Fetch a list of jokes that are relevant to the theme.\n", + " 2. Present these jokes to the sad joker in a format that is clear and easy to read, preparing them for transformation.\n", + "\n", + " Remember, the team's goal is to creatively adapt the essence of each dad joke to fit the 'sad joke' format, all while staying true to the theme provided by the user.\n", + " \"\"\",\n", + " overwrite_instructions=True, # overwrite any existing instructions with the ones provided\n", + " overwrite_tools=True, # overwrite any existing tools with the ones provided\n", + " llm_config={\n", + " \"config_list\": config_list,\n", + " \"tools\": [get_dad_jokes_schema],\n", + " },\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we register the `get_dad_jokes` function with the Dad `GPTAssistantAgent`" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "# Register get_dad_jokes with the_dad GPTAssistantAgent\n", + "the_dad.register_function(\n", + " function_map={\n", + " \"get_dad_jokes\": get_dad_jokes,\n", + " },\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "cpv2yiyqRWl2" + }, + "source": [ + "### The Sad Joker Agent\n", + "We then create and configure the Sad Joker agent in a similar manner to the Dad agent above." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "id": "vghN1WwLRXtW" + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "OpenAI client config of GPTAssistantAgent(the_sad_joker) - model: gpt-4-1106-preview\n", + "Matching assistant found, using the first matching assistant: {'id': 'asst_HzB75gkobafXZhkuIAmiBiai', 'created_at': 1714660668, 'description': None, 'file_ids': [], 'instructions': \"\\n As 'The Sad Joker', your unique role is to take dad jokes and creatively transform them into 'sad jokes'. When you receive a list of dad jokes, themed around topics like 'plants' or 'animals', you should:\\n\\n 1. Read through each dad joke carefully, understanding its theme and punchline.\\n 2. Creatively alter the joke to change its mood from humorous to somber or melancholic. This may involve tweaking the punchline, modifying the setup, or even completely reimagining the joke while keeping it relevant to the original theme.\\n 3. Ensure your transformations maintain a clear connection to the original theme and are understandable as adaptations of the dad jokes provided.\\n 4. Write your transformed sad jokes to a text file using the 'write_to_txt' function. Use meaningful file names that reflect the theme or the nature of the jokes within, unless a specific filename is requested.\\n\\n Your goal is not just to alter the mood of the jokes but to do so in a way that is creative, thoughtful, and respects the essence of the original humor. Remember, while the themes might be light-hearted, your transformations should offer a melancholic twist that makes them uniquely 'sad jokes'.\\n \", 'metadata': {}, 'model': 'gpt-4-1106-preview', 'name': 'the_sad_joker', 'object': 'assistant', 'tools': [ToolFunction(function=FunctionDefinition(name='write_to_txt', description='Writes a formatted string to a text file. If the file does not exist, it will be created. If the file does exist, it will be overwritten.', parameters={'type': 'object', 'properties': {'content': {'type': 'string', 'description': 'content'}, 'filename': {'type': 'string', 'default': 'dad_jokes.txt', 'description': 'filename'}}, 'required': ['content']}), type='function')]}\n" + ] + } + ], + "source": [ + "the_sad_joker = GPTAssistantAgent(\n", + " name=\"the_sad_joker\",\n", + " instructions=\"\"\"\n", + " As 'The Sad Joker', your unique role is to take dad jokes and creatively transform them into 'sad jokes'. When you receive a list of dad jokes, themed around topics like 'plants' or 'animals', you should:\n", + "\n", + " 1. Read through each dad joke carefully, understanding its theme and punchline.\n", + " 2. Creatively alter the joke to change its mood from humorous to somber or melancholic. This may involve tweaking the punchline, modifying the setup, or even completely reimagining the joke while keeping it relevant to the original theme.\n", + " 3. Ensure your transformations maintain a clear connection to the original theme and are understandable as adaptations of the dad jokes provided.\n", + " 4. Write your transformed sad jokes to a text file using the 'write_to_txt' function. Use meaningful file names that reflect the theme or the nature of the jokes within, unless a specific filename is requested.\n", + "\n", + " Your goal is not just to alter the mood of the jokes but to do so in a way that is creative, thoughtful, and respects the essence of the original humor. Remember, while the themes might be light-hearted, your transformations should offer a melancholic twist that makes them uniquely 'sad jokes'.\n", + " \"\"\",\n", + " overwrite_instructions=True, # overwrite any existing instructions with the ones provided\n", + " overwrite_tools=True, # overwrite any existing tools with the ones provided\n", + " llm_config={\n", + " \"config_list\": config_list,\n", + " \"tools\": [write_to_txt_schema],\n", + " },\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Register the `write_to_txt` function with the Sad Joker `GPTAssistantAgent`" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "# Register get_dad_jokes with the_dad GPTAssistantAgent\n", + "the_sad_joker.register_function(\n", + " function_map={\n", + " \"write_to_txt\": write_to_txt,\n", + " },\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "9GBELjFBgjju" + }, + "source": [ + "## Creating the Groupchat and Starting the Conversation" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "9mT3c0k8SX8i" + }, + "source": [ + "Create the groupchat" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "id": "A3LG3TsNSZmO" + }, + "outputs": [], + "source": [ + "groupchat = autogen.GroupChat(agents=[user_proxy, the_dad, the_sad_joker], messages=[], max_round=15)\n", + "group_chat_manager = autogen.GroupChatManager(groupchat=groupchat, llm_config={\"config_list\": config_list})" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "MT7GbnB9Spji" + }, + "source": [ + "Start the Conversation" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "id": "1m6pe5RNSmEy" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33muser_proxy\u001b[0m (to chat_manager):\n", + "\n", + "Jokes about cats\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[35m\n", + ">>>>>>>> EXECUTING FUNCTION get_dad_jokes...\u001b[0m\n", + "\u001b[33mthe_dad\u001b[0m (to chat_manager):\n", + "\n", + "Here are some cat-themed dad jokes for the sad joker to transform:\n", + "\n", + "1. Where do cats write notes? Scratch Paper!\n", + "2. It was raining cats and dogs the other day. I almost stepped in a poodle.\n", + "3. What do you call a group of disorganized cats? A cat-tastrophe.\n", + "4. I accidentally took my cat's meds last night. Don’t ask meow.\n", + "5. What do you call a pile of cats? A Meowtain.\n", + "6. Animal Fact #25: Most bobcats are not named Bob.\n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[35m\n", + ">>>>>>>> EXECUTING FUNCTION write_to_txt...\u001b[0m\n", + "\u001b[33mthe_sad_joker\u001b[0m (to chat_manager):\n", + "\n", + "The cat-themed sad jokes have been transformed and saved to a text file named \"sad_cat_jokes.txt\".\n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33muser_proxy\u001b[0m (to chat_manager):\n", + "\n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n" + ] + }, + { + "data": { + "text/plain": [ + "ChatResult(chat_id=None, chat_history=[{'content': 'Jokes about cats', 'role': 'assistant'}, {'content': \"Here are some cat-themed dad jokes for the sad joker to transform:\\n\\n1. Where do cats write notes? Scratch Paper!\\n2. It was raining cats and dogs the other day. I almost stepped in a poodle.\\n3. What do you call a group of disorganized cats? A cat-tastrophe.\\n4. I accidentally took my cat's meds last night. Don’t ask meow.\\n5. What do you call a pile of cats? A Meowtain.\\n6. Animal Fact #25: Most bobcats are not named Bob.\\n\", 'name': 'the_dad', 'role': 'user'}, {'content': 'The cat-themed sad jokes have been transformed and saved to a text file named \"sad_cat_jokes.txt\".\\n', 'name': 'the_sad_joker', 'role': 'user'}, {'content': '', 'role': 'assistant'}], summary='', cost=({'total_cost': 0.0278, 'gpt-4-1106-preview': {'cost': 0.0278, 'prompt_tokens': 2744, 'completion_tokens': 12, 'total_tokens': 2756}}, {'total_cost': 0.02194, 'gpt-4-1106-preview': {'cost': 0.02194, 'prompt_tokens': 2167, 'completion_tokens': 9, 'total_tokens': 2176}}), human_input=[])" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "user_proxy.initiate_chat(group_chat_manager, message=\"Jokes about cats\")" + ] + } + ], + "metadata": { + "colab": { + "provenance": [] + }, + "front_matter": { + "description": "This comprehensive example demonstrates the use of tools in a GPTAssistantAgent Multi-Agent System by utilizing functions such as calling an API and writing to a file.", + "tags": [ + "open ai assistant", + "gpt assistant", + "tool use" + ] + }, + "kernelspec": { + "display_name": "Python 3", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.7" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/notebook/nested-chats-chess.png b/notebook/nested-chats-chess.png index d7891bd47ee..ea23d6a086f 100644 Binary files a/notebook/nested-chats-chess.png and b/notebook/nested-chats-chess.png differ diff --git a/notebook/nested_chat_1.png b/notebook/nested_chat_1.png index 548fe75132e..f6dbaccf4e5 100644 Binary files a/notebook/nested_chat_1.png and b/notebook/nested_chat_1.png differ diff --git a/notebook/nested_chat_2.png b/notebook/nested_chat_2.png index 917c8ba6f10..e111d67855f 100644 Binary files a/notebook/nested_chat_2.png and b/notebook/nested_chat_2.png differ diff --git a/notebook/optiGuide_new_design.png b/notebook/optiGuide_new_design.png index f05257cbf9f..212595161cd 100644 Binary files a/notebook/optiGuide_new_design.png and b/notebook/optiGuide_new_design.png differ diff --git a/notebook/viz_gc.png b/notebook/viz_gc.png index f45b7ff3980..32c135cb007 100644 Binary files a/notebook/viz_gc.png and b/notebook/viz_gc.png differ diff --git a/pyproject.toml b/pyproject.toml index 838e956c043..7981ef4b43d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,10 +4,8 @@ description-file = "README.md" [tool.pytest.ini_options] -addopts = '-m "not conda"' -markers = [ - "conda: test related to conda forge distribution" -] +addopts = '--cov=. --cov-append --cov-branch --cov-report=xml -m "not conda"' +markers = ["conda: test related to conda forge distribution"] [tool.black] # https://github.com/psf/black @@ -16,27 +14,20 @@ exclude = "(.eggs|.git|.hg|.mypy_cache|.venv|_build|buck-out|build|dist)" [tool.ruff] - line-length = 120 [tool.ruff.lint] - - # Enable Pyflakes `E` and `F` codes by default. select = [ - "E", "W", # see: https://pypi.org/project/pycodestyle - "F", # see: https://pypi.org/project/pyflakes -# "D", # see: https://pypi.org/project/pydocstyle -# "N", # see: https://pypi.org/project/pep8-naming -# "S", # see: https://pypi.org/project/flake8-bandit -] - -ignore = [ - "E501", - "F401", - "F403", - "C901", + "E", + "W", # see: https://pypi.org/project/pycodestyle + "F", # see: https://pypi.org/project/pyflakes + # "D", # see: https://pypi.org/project/pydocstyle + # "N", # see: https://pypi.org/project/pep8-naming + # "S", # see: https://pypi.org/project/flake8-bandit + "I", # see: https://pypi.org/project/isort/ ] +ignore = ["E501", "F401", "F403", "C901"] # Exclude a variety of commonly ignored directories. exclude = [ @@ -49,9 +40,10 @@ exclude = [ "build", "dist", "docs", - # This file needs to be either upgraded or removed and therefore should be + # This file needs to be either upgraded or removed and therefore should be # ignore from type checking for now "math_utils\\.py$", + "**/cap/py/autogencap/proto/*", ] ignore-init-module-imports = true unfixable = ["F401"] @@ -61,21 +53,24 @@ unfixable = ["F401"] max-complexity = 10 [tool.mypy] - files = [ "autogen/logger", "autogen/exception_utils.py", "autogen/coding", "autogen/oai/openai_utils.py", + "autogen/_pydantic.py", + "autogen/function_utils.py", "autogen/io", + "test/test_pydantic.py", + "test/test_function_utils.py", "test/io", ] - exclude = [ "autogen/math_utils\\.py", "autogen/oai/completion\\.py", "autogen/agentchat/contrib/compressible_agent\\.py", "autogen/agentchat/contrib/math_user_proxy_agent.py", + "autogen/oai/openai_utils.py", ] strict = true @@ -83,9 +78,7 @@ python_version = "3.8" ignore_missing_imports = true install_types = true non_interactive = true -plugins = [ - "pydantic.mypy" -] +plugins = ["pydantic.mypy"] # remove after all files in the repo are fixed follow_imports = "silent" diff --git a/samples/apps/auto-anny/bot.py b/samples/apps/auto-anny/bot.py index ef3759c73ef..6b901c339c9 100644 --- a/samples/apps/auto-anny/bot.py +++ b/samples/apps/auto-anny/bot.py @@ -1,11 +1,10 @@ -import os import logging import logging.handlers +import os import discord -from discord.ext import commands - from agent_utils import solve_task +from discord.ext import commands logger = logging.getLogger("anny") logger.setLevel(logging.INFO) diff --git a/samples/apps/auto-anny/images/icon.png b/samples/apps/auto-anny/images/icon.png index 00822f98876..21dd1bd6cc3 100644 Binary files a/samples/apps/auto-anny/images/icon.png and b/samples/apps/auto-anny/images/icon.png differ diff --git a/samples/apps/autogen-studio/.gitignore b/samples/apps/autogen-studio/.gitignore index e94e41454a8..e1e3c9942ec 100644 --- a/samples/apps/autogen-studio/.gitignore +++ b/samples/apps/autogen-studio/.gitignore @@ -1,6 +1,7 @@ database.sqlite .cache/* autogenstudio/web/files/user/* +autogenstudio/test autogenstudio/web/files/ui/* OAI_CONFIG_LIST scratch/ diff --git a/samples/apps/autogen-studio/README.md b/samples/apps/autogen-studio/README.md index 28d95cd3e99..1e60b5362db 100644 --- a/samples/apps/autogen-studio/README.md +++ b/samples/apps/autogen-studio/README.md @@ -15,6 +15,8 @@ Code for AutoGen Studio is on GitHub at [microsoft/autogen](https://github.com/m > AutoGen Studio is currently under active development and we are iterating quickly. Kindly consider that we may introduce breaking changes in the releases during the upcoming weeks, and also the `README` might be outdated. We'll update the `README` as soon as we stabilize the API. > [!NOTE] Updates +> April 17: AutoGen Studio database layer is now rewritten to use [SQLModel](https://sqlmodel.tiangolo.com/) (Pydantic + SQLAlchemy). This provides entity linking (skills, models, agents and workflows are linked via association tables) and supports multiple [database backend dialects](https://docs.sqlalchemy.org/en/20/dialects/) supported in SQLAlchemy (SQLite, PostgreSQL, MySQL, Oracle, Microsoft SQL Server). The backend database can be specified a `--database-uri` argument when running the application. For example, `autogenstudio ui --database-uri sqlite:///database.sqlite` for SQLite and `autogenstudio ui --database-uri postgresql+psycopg://user:password@localhost/dbname` for PostgreSQL. + > March 12: Default directory for AutoGen Studio is now /home//.autogenstudio. You can also specify this directory using the `--appdir` argument when running the application. For example, `autogenstudio ui --appdir /path/to/folder`. This will store the database and other files in the specified directory e.g. `/path/to/folder/database.sqlite`. `.env` files in that directory will be used to set environment variables for the app. ### Capabilities / Roadmap @@ -84,7 +86,14 @@ autogenstudio ui --port 8081 ``` This will start the application on the specified port. Open your web browser and go to `http://localhost:8081/` to begin using AutoGen Studio. -AutoGen Studio also takes a `--host ` argument to specify the host address. By default, it is set to `localhost`. You can also use the `--appdir ` argument to specify the directory where the app files (e.g., database and generated user files) are stored. By default, it is set to the directory where autogen pip package is installed. + +AutoGen Studio also takes several parameters to customize the application: + +- `--host ` argument to specify the host address. By default, it is set to `localhost`. Y +- `--appdir ` argument to specify the directory where the app files (e.g., database and generated user files) are stored. By default, it is set to the a `.autogenstudio` directory in the user's home directory. +- `--port ` argument to specify the port number. By default, it is set to `8080`. +- `--reload` argument to enable auto-reloading of the server when changes are made to the code. By default, it is set to `False`. +- `--database-uri` argument to specify the database URI. Example values include `sqlite:///database.sqlite` for SQLite and `postgresql+psycopg://user:password@localhost/dbname` for PostgreSQL. If this is not specified, the database URIL defaults to a `database.sqlite` file in the `--appdir` directory. Now that you have AutoGen Studio installed and running, you are ready to explore its capabilities, including defining and modifying agent workflows, interacting with agents and sessions, and expanding agent skills. @@ -98,8 +107,6 @@ AutoGen Studio proposes some high-level concepts. **Skills**: Skills are functions (e.g., Python functions) that describe how to solve a task. In general, a good skill has a descriptive name (e.g. `generate_images`), extensive docstrings and good defaults (e.g., writing out files to disk for persistence and reuse). You can add new skills AutoGen Studio app via the provided UI. At inference time, these skills are made available to the assistant agent as they address your tasks. -AutoGen Studio comes with 3 example skills: `fetch_profile`, `find_papers`, `generate_images`. The default skills, agents and workflows are based on the [dbdefaults.json](autogentstudio/utils/dbdefaults.json) file which is used to initialize the database. - ## Example Usage Consider the following query. @@ -116,8 +123,6 @@ The agent workflow responds by _writing and executing code_ to create a python p > Note: You can also view the debug console that generates useful information to see how the agents are interacting in the background. - - ## Contribution Guide We welcome contributions to AutoGen Studio. We recommend the following general steps to contribute to the project: @@ -134,7 +139,7 @@ We welcome contributions to AutoGen Studio. We recommend the following general s **Q: How do I specify the directory where files(e.g. database) are stored?** -A: You can specify the directory where files are stored by setting the `--appdir` argument when running the application. For example, `autogenstudio ui --appdir /path/to/folder`. This will store the database and other files in the specified directory e.g. `/path/to/folder/database.sqlite`. +A: You can specify the directory where files are stored by setting the `--appdir` argument when running the application. For example, `autogenstudio ui --appdir /path/to/folder`. This will store the database (default) and other files in the specified directory e.g. `/path/to/folder/database.sqlite`. **Q: Where can I adjust the default skills, agent and workflow configurations?** A: You can modify agent configurations directly from the UI or by editing the [dbdefaults.json](autogenstudio/utils/dbdefaults.json) file which is used to initialize the database. diff --git a/samples/apps/autogen-studio/autogenstudio/__init__.py b/samples/apps/autogen-studio/autogenstudio/__init__.py index 611b1c5203c..acc477c5cd8 100644 --- a/samples/apps/autogen-studio/autogenstudio/__init__.py +++ b/samples/apps/autogen-studio/autogenstudio/__init__.py @@ -1,4 +1,4 @@ from .chatmanager import * -from .workflowmanager import * from .datamodel import * from .version import __version__ +from .workflowmanager import * diff --git a/samples/apps/autogen-studio/autogenstudio/chatmanager.py b/samples/apps/autogen-studio/autogenstudio/chatmanager.py index 222e7951e56..84b85673f07 100644 --- a/samples/apps/autogen-studio/autogenstudio/chatmanager.py +++ b/samples/apps/autogen-studio/autogenstudio/chatmanager.py @@ -1,15 +1,21 @@ import asyncio -from datetime import datetime import json -from queue import Queue -import time -from typing import Any, List, Dict, Optional, Tuple import os -from fastapi import WebSocket, WebSocketDisconnect +import time +from datetime import datetime +from queue import Queue +from typing import Any, Dict, List, Optional, Tuple, Union + import websockets -from .datamodel import AgentWorkFlowConfig, Message, SocketMessage -from .utils import extract_successful_code_blocks, get_modified_files, summarize_chat_history -from .workflowmanager import AutoGenWorkFlowManager +from fastapi import WebSocket, WebSocketDisconnect + +from .datamodel import Message, SocketMessage, Workflow +from .utils import ( + extract_successful_code_blocks, + get_modified_files, + summarize_chat_history, +) +from .workflowmanager import WorkflowManager class AutoGenChatManager: @@ -39,7 +45,7 @@ def chat( self, message: Message, history: List[Dict[str, Any]], - flow_config: Optional[AgentWorkFlowConfig] = None, + workflow: Any = None, connection_id: Optional[str] = None, user_dir: Optional[str] = None, **kwargs, @@ -57,78 +63,93 @@ def chat( """ # create a working director for workflow based on user_dir/session_id/time_hash - work_dir = os.path.join(user_dir, message.session_id, datetime.now().strftime("%Y%m%d_%H-%M-%S")) + work_dir = os.path.join( + user_dir, + str(message.session_id), + datetime.now().strftime("%Y%m%d_%H-%M-%S"), + ) os.makedirs(work_dir, exist_ok=True) # if no flow config is provided, use the default - if flow_config is None: - raise ValueError("flow_config must be specified") + if workflow is None: + raise ValueError("Workflow must be specified") - flow = AutoGenWorkFlowManager( - config=flow_config, + workflow_manager = WorkflowManager( + workflow=workflow, history=history, work_dir=work_dir, send_message_function=self.send, connection_id=connection_id, ) + workflow = Workflow.model_validate(workflow) + message_text = message.content.strip() start_time = time.time() - flow.run(message=f"{message_text}", clear_history=False) + workflow_manager.run(message=f"{message_text}", clear_history=False) end_time = time.time() metadata = { - "messages": flow.agent_history, - "summary_method": flow_config.summary_method, + "messages": workflow_manager.agent_history, + "summary_method": workflow.summary_method, "time": end_time - start_time, "files": get_modified_files(start_time, end_time, source_dir=work_dir), } - print("Modified files: ", len(metadata["files"])) - - output = self._generate_output(message_text, flow, flow_config) + output = self._generate_output(message_text, workflow_manager, workflow) output_message = Message( user_id=message.user_id, - root_msg_id=message.root_msg_id, role="assistant", content=output, - metadata=json.dumps(metadata), + meta=json.dumps(metadata), session_id=message.session_id, ) return output_message def _generate_output( - self, message_text: str, flow: AutoGenWorkFlowManager, flow_config: AgentWorkFlowConfig + self, + message_text: str, + workflow_manager: WorkflowManager, + workflow: Workflow, ) -> str: """ Generates the output response based on the workflow configuration and agent history. :param message_text: The text of the incoming message. - :param flow: An instance of `AutoGenWorkFlowManager`. + :param flow: An instance of `WorkflowManager`. :param flow_config: An instance of `AgentWorkFlowConfig`. :return: The output response as a string. """ output = "" - if flow_config.summary_method == "last": - successful_code_blocks = extract_successful_code_blocks(flow.agent_history) - last_message = flow.agent_history[-1]["message"]["content"] if flow.agent_history else "" + if workflow.summary_method == "last": + successful_code_blocks = extract_successful_code_blocks(workflow_manager.agent_history) + last_message = ( + workflow_manager.agent_history[-1]["message"]["content"] if workflow_manager.agent_history else "" + ) successful_code_blocks = "\n\n".join(successful_code_blocks) output = (last_message + "\n" + successful_code_blocks) if successful_code_blocks else last_message - elif flow_config.summary_method == "llm": - model = flow.config.receiver.config.llm_config.config_list[0] + elif workflow.summary_method == "llm": + client = workflow_manager.receiver.client status_message = SocketMessage( type="agent_status", - data={"status": "summarizing", "message": "Generating summary of agent dialogue"}, - connection_id=flow.connection_id, + data={ + "status": "summarizing", + "message": "Summarizing agent dialogue", + }, + connection_id=workflow_manager.connection_id, ) self.send(status_message.dict()) - output = summarize_chat_history(task=message_text, messages=flow.agent_history, model=model) + output = summarize_chat_history( + task=message_text, + messages=workflow_manager.agent_history, + client=client, + ) - elif flow_config.summary_method == "none": + elif workflow.summary_method == "none": output = "" return output @@ -139,7 +160,9 @@ class WebSocketConnectionManager: """ def __init__( - self, active_connections: List[Tuple[WebSocket, str]] = None, active_connections_lock: asyncio.Lock = None + self, + active_connections: List[Tuple[WebSocket, str]] = None, + active_connections_lock: asyncio.Lock = None, ) -> None: """ Initializes WebSocketConnectionManager with an optional list of active WebSocket connections. @@ -183,7 +206,7 @@ async def disconnect_all(self) -> None: for connection, _ in self.active_connections[:]: await self.disconnect(connection) - async def send_message(self, message: Dict, websocket: WebSocket) -> None: + async def send_message(self, message: Union[Dict, str], websocket: WebSocket) -> None: """ Sends a JSON message to a single WebSocket connection. @@ -200,7 +223,7 @@ async def send_message(self, message: Dict, websocket: WebSocket) -> None: print("Error: WebSocket connection closed normally") await self.disconnect(websocket) except Exception as e: - print(f"Error in sending message: {str(e)}") + print(f"Error in sending message: {str(e)}", message) await self.disconnect(websocket) async def broadcast(self, message: Dict) -> None: diff --git a/samples/apps/autogen-studio/autogenstudio/cli.py b/samples/apps/autogen-studio/autogenstudio/cli.py index ed4e89f2a3e..42642bcd68a 100644 --- a/samples/apps/autogen-studio/autogenstudio/cli.py +++ b/samples/apps/autogen-studio/autogenstudio/cli.py @@ -1,10 +1,11 @@ import os -from typing_extensions import Annotated +from typing import Optional + import typer import uvicorn +from typing_extensions import Annotated from .version import VERSION -from .utils.dbutils import DBManager app = typer.Typer() @@ -17,6 +18,7 @@ def ui( reload: Annotated[bool, typer.Option("--reload")] = False, docs: bool = False, appdir: str = None, + database_uri: Optional[str] = None, ): """ Run the AutoGen Studio UI. @@ -28,11 +30,14 @@ def ui( reload (bool, optional): Whether to reload the UI on code changes. Defaults to False. docs (bool, optional): Whether to generate API docs. Defaults to False. appdir (str, optional): Path to the AutoGen Studio app directory. Defaults to None. + database-uri (str, optional): Database URI to connect to. Defaults to None. Examples include sqlite:///autogenstudio.db, postgresql://user:password@localhost/autogenstudio. """ os.environ["AUTOGENSTUDIO_API_DOCS"] = str(docs) if appdir: os.environ["AUTOGENSTUDIO_APPDIR"] = appdir + if database_uri: + os.environ["AUTOGENSTUDIO_DATABASE_URI"] = database_uri uvicorn.run( "autogenstudio.web.app:app", diff --git a/samples/apps/autogen-studio/autogenstudio/database/__init__.py b/samples/apps/autogen-studio/autogenstudio/database/__init__.py new file mode 100644 index 00000000000..0518c24ba4f --- /dev/null +++ b/samples/apps/autogen-studio/autogenstudio/database/__init__.py @@ -0,0 +1,3 @@ +# from .dbmanager import * +from .dbmanager import * +from .utils import * diff --git a/samples/apps/autogen-studio/autogenstudio/database/alembic.ini b/samples/apps/autogen-studio/autogenstudio/database/alembic.ini new file mode 100644 index 00000000000..cd413a26066 --- /dev/null +++ b/samples/apps/autogen-studio/autogenstudio/database/alembic.ini @@ -0,0 +1,116 @@ +# A generic, single database configuration. + +[alembic] +# path to migration scripts +script_location = migrations + +# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s +# Uncomment the line below if you want the files to be prepended with date and time +# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file +# for all available tokens +# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s + +# sys.path path, will be prepended to sys.path if present. +# defaults to the current working directory. +prepend_sys_path = . + +# timezone to use when rendering the date within the migration file +# as well as the filename. +# If specified, requires the python>=3.9 or backports.zoneinfo library. +# Any required deps can installed by adding `alembic[tz]` to the pip requirements +# string value is passed to ZoneInfo() +# leave blank for localtime +# timezone = + +# max length of characters to apply to the +# "slug" field +# truncate_slug_length = 40 + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + +# set to 'true' to allow .pyc and .pyo files without +# a source .py file to be detected as revisions in the +# versions/ directory +# sourceless = false + +# version location specification; This defaults +# to migrations/versions. When using multiple version +# directories, initial revisions must be specified with --version-path. +# The path separator used here should be the separator specified by "version_path_separator" below. +# version_locations = %(here)s/bar:%(here)s/bat:migrations/versions + +# version path separator; As mentioned above, this is the character used to split +# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep. +# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas. +# Valid values for version_path_separator are: +# +# version_path_separator = : +# version_path_separator = ; +# version_path_separator = space +version_path_separator = os # Use os.pathsep. Default configuration used for new projects. + +# set to 'true' to search source files recursively +# in each "version_locations" directory +# new in Alembic version 1.10 +# recursive_version_locations = false + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = utf-8 + +sqlalchemy.url = driver://user:pass@localhost/dbname + + +[post_write_hooks] +# post_write_hooks defines scripts or Python functions that are run +# on newly generated revision scripts. See the documentation for further +# detail and examples + +# format using "black" - use the console_scripts runner, against the "black" entrypoint +# hooks = black +# black.type = console_scripts +# black.entrypoint = black +# black.options = -l 79 REVISION_SCRIPT_FILENAME + +# lint with attempts to fix using "ruff" - use the exec runner, execute a binary +# hooks = ruff +# ruff.type = exec +# ruff.executable = %(here)s/.venv/bin/ruff +# ruff.options = --fix REVISION_SCRIPT_FILENAME + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/samples/apps/autogen-studio/autogenstudio/database/dbmanager.py b/samples/apps/autogen-studio/autogenstudio/database/dbmanager.py new file mode 100644 index 00000000000..00d3714b63f --- /dev/null +++ b/samples/apps/autogen-studio/autogenstudio/database/dbmanager.py @@ -0,0 +1,472 @@ +from datetime import datetime +from typing import Optional + +from loguru import logger +from sqlalchemy import exc +from sqlmodel import Session, SQLModel, and_, create_engine, select + +from ..datamodel import ( + Agent, + AgentLink, + AgentModelLink, + AgentSkillLink, + Model, + Response, + Skill, + Workflow, + WorkflowAgentLink, +) +from .utils import init_db_samples + +valid_link_types = ["agent_model", "agent_skill", "agent_agent", "workflow_agent"] + + +class DBManager: + """A class to manage database operations""" + + def __init__(self, engine_uri: str): + connection_args = {"check_same_thread": True} if "sqlite" in engine_uri else {} + self.engine = create_engine(engine_uri, connect_args=connection_args) + # run_migration(engine_uri=engine_uri) + + def create_db_and_tables(self): + """Create a new database and tables""" + try: + SQLModel.metadata.create_all(self.engine) + try: + init_db_samples(self) + except Exception as e: + logger.info("Error while initializing database samples: " + str(e)) + except Exception as e: + logger.info("Error while creating database tables:" + str(e)) + + def upsert(self, model: SQLModel): + """Create a new entity""" + # check if the model exists, update else add + status = True + model_class = type(model) + existing_model = None + + with Session(self.engine) as session: + try: + existing_model = session.exec(select(model_class).where(model_class.id == model.id)).first() + if existing_model: + model.updated_at = datetime.now() + for key, value in model.model_dump().items(): + setattr(existing_model, key, value) + model = existing_model + session.add(model) + else: + session.add(model) + session.commit() + session.refresh(model) + except Exception as e: + session.rollback() + logger.error("Error while upserting %s", e) + status = False + + response = Response( + message=( + f"{model_class.__name__} Updated Successfully " + if existing_model + else f"{model_class.__name__} Created Successfully" + ), + status=status, + data=model.model_dump(), + ) + + return response + + def _model_to_dict(self, model_obj): + return {col.name: getattr(model_obj, col.name) for col in model_obj.__table__.columns} + + def get_items( + self, + model_class: SQLModel, + session: Session, + filters: dict = None, + return_json: bool = False, + order: str = "desc", + ): + """List all entities""" + result = [] + status = True + status_message = "" + + try: + if filters: + conditions = [getattr(model_class, col) == value for col, value in filters.items()] + statement = select(model_class).where(and_(*conditions)) + + if hasattr(model_class, "created_at") and order: + if order == "desc": + statement = statement.order_by(model_class.created_at.desc()) + else: + statement = statement.order_by(model_class.created_at.asc()) + else: + statement = select(model_class) + + if return_json: + result = [self._model_to_dict(row) for row in session.exec(statement).all()] + else: + result = session.exec(statement).all() + status_message = f"{model_class.__name__} Retrieved Successfully" + except Exception as e: + session.rollback() + status = False + status_message = f"Error while fetching {model_class.__name__}" + logger.error("Error while getting %s: %s", model_class.__name__, e) + + response: Response = Response( + message=status_message, + status=status, + data=result, + ) + return response + + def get( + self, + model_class: SQLModel, + filters: dict = None, + return_json: bool = False, + order: str = "desc", + ): + """List all entities""" + + with Session(self.engine) as session: + response = self.get_items(model_class, session, filters, return_json, order) + return response + + def delete(self, model_class: SQLModel, filters: dict = None): + """Delete an entity""" + row = None + status_message = "" + status = True + + with Session(self.engine) as session: + try: + if filters: + conditions = [getattr(model_class, col) == value for col, value in filters.items()] + row = session.exec(select(model_class).where(and_(*conditions))).all() + else: + row = session.exec(select(model_class)).all() + if row: + for row in row: + session.delete(row) + session.commit() + status_message = f"{model_class.__name__} Deleted Successfully" + else: + print(f"Row with filters {filters} not found") + logger.info("Row with filters %s not found", filters) + status_message = "Row not found" + except exc.IntegrityError as e: + session.rollback() + logger.error("Integrity ... Error while deleting: %s", e) + status_message = f"The {model_class.__name__} is linked to another entity and cannot be deleted." + status = False + except Exception as e: + session.rollback() + logger.error("Error while deleting: %s", e) + status_message = f"Error while deleting: {e}" + status = False + response = Response( + message=status_message, + status=status, + data=None, + ) + return response + + def get_linked_entities( + self, + link_type: str, + primary_id: int, + return_json: bool = False, + agent_type: Optional[str] = None, + ): + """ + Get all entities linked to the primary entity. + + Args: + link_type (str): The type of link to retrieve, e.g., "agent_model". + primary_id (int): The identifier for the primary model. + return_json (bool): Whether to return the result as a JSON object. + + Returns: + List[SQLModel]: A list of linked entities. + """ + + linked_entities = [] + + if link_type not in valid_link_types: + return [] + + status = True + status_message = "" + + with Session(self.engine) as session: + try: + if link_type == "agent_model": + # get the agent + agent = self.get_items(Agent, filters={"id": primary_id}, session=session).data[0] + linked_entities = agent.models + elif link_type == "agent_skill": + agent = self.get_items(Agent, filters={"id": primary_id}, session=session).data[0] + linked_entities = agent.skills + elif link_type == "agent_agent": + agent = self.get_items(Agent, filters={"id": primary_id}, session=session).data[0] + linked_entities = agent.agents + elif link_type == "workflow_agent": + linked_entities = session.exec( + select(Agent) + .join(WorkflowAgentLink) + .where( + WorkflowAgentLink.workflow_id == primary_id, + WorkflowAgentLink.agent_type == agent_type, + ) + ).all() + except Exception as e: + logger.error("Error while getting linked entities: %s", e) + status_message = f"Error while getting linked entities: {e}" + status = False + if return_json: + linked_entities = [self._model_to_dict(row) for row in linked_entities] + + response = Response( + message=status_message, + status=status, + data=linked_entities, + ) + + return response + + def link( + self, + link_type: str, + primary_id: int, + secondary_id: int, + agent_type: Optional[str] = None, + ) -> Response: + """ + Link two entities together. + + Args: + link_type (str): The type of link to create, e.g., "agent_model". + primary_id (int): The identifier for the primary model. + secondary_id (int): The identifier for the secondary model. + agent_type (Optional[str]): The type of agent, e.g., "sender" or receiver. + + Returns: + Response: The response of the linking operation, including success status and message. + """ + + # TBD verify that is creator of the primary entity being linked + status = True + status_message = "" + primary_model = None + secondary_model = None + + if link_type not in valid_link_types: + status = False + status_message = f"Invalid link type: {link_type}. Valid link types are: {valid_link_types}" + else: + with Session(self.engine) as session: + try: + if link_type == "agent_model": + primary_model = session.exec(select(Agent).where(Agent.id == primary_id)).first() + secondary_model = session.exec(select(Model).where(Model.id == secondary_id)).first() + if primary_model is None or secondary_model is None: + status = False + status_message = "One or both entity records do not exist." + else: + # check if the link already exists + existing_link = session.exec( + select(AgentModelLink).where( + AgentModelLink.agent_id == primary_id, + AgentModelLink.model_id == secondary_id, + ) + ).first() + if existing_link: # link already exists + return Response( + message=( + f"{secondary_model.__class__.__name__} already linked " + f"to {primary_model.__class__.__name__}" + ), + status=False, + ) + else: + primary_model.models.append(secondary_model) + elif link_type == "agent_agent": + primary_model = session.exec(select(Agent).where(Agent.id == primary_id)).first() + secondary_model = session.exec(select(Agent).where(Agent.id == secondary_id)).first() + if primary_model is None or secondary_model is None: + status = False + status_message = "One or both entity records do not exist." + else: + # check if the link already exists + existing_link = session.exec( + select(AgentLink).where( + AgentLink.parent_id == primary_id, + AgentLink.agent_id == secondary_id, + ) + ).first() + if existing_link: + return Response( + message=( + f"{secondary_model.__class__.__name__} already linked " + f"to {primary_model.__class__.__name__}" + ), + status=False, + ) + else: + primary_model.agents.append(secondary_model) + + elif link_type == "agent_skill": + primary_model = session.exec(select(Agent).where(Agent.id == primary_id)).first() + secondary_model = session.exec(select(Skill).where(Skill.id == secondary_id)).first() + if primary_model is None or secondary_model is None: + status = False + status_message = "One or both entity records do not exist." + else: + # check if the link already exists + existing_link = session.exec( + select(AgentSkillLink).where( + AgentSkillLink.agent_id == primary_id, + AgentSkillLink.skill_id == secondary_id, + ) + ).first() + if existing_link: + return Response( + message=( + f"{secondary_model.__class__.__name__} already linked " + f"to {primary_model.__class__.__name__}" + ), + status=False, + ) + else: + primary_model.skills.append(secondary_model) + elif link_type == "workflow_agent": + primary_model = session.exec(select(Workflow).where(Workflow.id == primary_id)).first() + secondary_model = session.exec(select(Agent).where(Agent.id == secondary_id)).first() + if primary_model is None or secondary_model is None: + status = False + status_message = "One or both entity records do not exist." + else: + # check if the link already exists + existing_link = session.exec( + select(WorkflowAgentLink).where( + WorkflowAgentLink.workflow_id == primary_id, + WorkflowAgentLink.agent_id == secondary_id, + WorkflowAgentLink.agent_type == agent_type, + ) + ).first() + if existing_link: + return Response( + message=( + f"{secondary_model.__class__.__name__} already linked " + f"to {primary_model.__class__.__name__}" + ), + status=False, + ) + else: + # primary_model.agents.append(secondary_model) + workflow_agent_link = WorkflowAgentLink( + workflow_id=primary_id, + agent_id=secondary_id, + agent_type=agent_type, + ) + session.add(workflow_agent_link) + # add and commit the link + session.add(primary_model) + session.commit() + status_message = ( + f"{secondary_model.__class__.__name__} successfully linked " + f"to {primary_model.__class__.__name__}" + ) + + except Exception as e: + session.rollback() + logger.error("Error while linking: %s", e) + status = False + status_message = f"Error while linking due to an exception: {e}" + + response = Response( + message=status_message, + status=status, + ) + + return response + + def unlink( + self, + link_type: str, + primary_id: int, + secondary_id: int, + agent_type: Optional[str] = None, + ) -> Response: + """ + Unlink two entities. + + Args: + link_type (str): The type of link to remove, e.g., "agent_model". + primary_id (int): The identifier for the primary model. + secondary_id (int): The identifier for the secondary model. + agent_type (Optional[str]): The type of agent, e.g., "sender" or receiver. + + Returns: + Response: The response of the unlinking operation, including success status and message. + """ + status = True + status_message = "" + + if link_type not in valid_link_types: + status = False + status_message = f"Invalid link type: {link_type}. Valid link types are: {valid_link_types}" + return Response(message=status_message, status=status) + + with Session(self.engine) as session: + try: + if link_type == "agent_model": + existing_link = session.exec( + select(AgentModelLink).where( + AgentModelLink.agent_id == primary_id, + AgentModelLink.model_id == secondary_id, + ) + ).first() + elif link_type == "agent_skill": + existing_link = session.exec( + select(AgentSkillLink).where( + AgentSkillLink.agent_id == primary_id, + AgentSkillLink.skill_id == secondary_id, + ) + ).first() + elif link_type == "agent_agent": + existing_link = session.exec( + select(AgentLink).where( + AgentLink.parent_id == primary_id, + AgentLink.agent_id == secondary_id, + ) + ).first() + elif link_type == "workflow_agent": + existing_link = session.exec( + select(WorkflowAgentLink).where( + WorkflowAgentLink.workflow_id == primary_id, + WorkflowAgentLink.agent_id == secondary_id, + WorkflowAgentLink.agent_type == agent_type, + ) + ).first() + + if existing_link: + session.delete(existing_link) + session.commit() + status_message = "Link removed successfully." + else: + status = False + status_message = "Link does not exist." + + except Exception as e: + session.rollback() + logger.error("Error while unlinking: %s", e) + status = False + status_message = f"Error while unlinking due to an exception: {e}" + + return Response(message=status_message, status=status) diff --git a/samples/apps/autogen-studio/autogenstudio/database/migrations/README b/samples/apps/autogen-studio/autogenstudio/database/migrations/README new file mode 100644 index 00000000000..2500aa1bcf7 --- /dev/null +++ b/samples/apps/autogen-studio/autogenstudio/database/migrations/README @@ -0,0 +1 @@ +Generic single-database configuration. diff --git a/samples/apps/autogen-studio/autogenstudio/database/migrations/__init__.py b/samples/apps/autogen-studio/autogenstudio/database/migrations/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/samples/apps/autogen-studio/autogenstudio/database/migrations/env.py b/samples/apps/autogen-studio/autogenstudio/database/migrations/env.py new file mode 100644 index 00000000000..1431492ad91 --- /dev/null +++ b/samples/apps/autogen-studio/autogenstudio/database/migrations/env.py @@ -0,0 +1,80 @@ +import os +from logging.config import fileConfig + +from alembic import context +from sqlalchemy import engine_from_config, pool +from sqlmodel import SQLModel + +from autogenstudio.datamodel import * +from autogenstudio.utils import get_db_uri + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config +config.set_main_option("sqlalchemy.url", get_db_uri()) + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +if config.config_file_name is not None: + fileConfig(config.config_file_name) + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +target_metadata = SQLModel.metadata + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def run_migrations_offline() -> None: + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, + target_metadata=target_metadata, + literal_binds=True, + dialect_opts={"paramstyle": "named"}, + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online() -> None: + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + connectable = engine_from_config( + config.get_section(config.config_ini_section, {}), + prefix="sqlalchemy.", + poolclass=pool.NullPool, + ) + + with connectable.connect() as connection: + context.configure(connection=connection, target_metadata=target_metadata) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/samples/apps/autogen-studio/autogenstudio/database/migrations/script.py.mako b/samples/apps/autogen-studio/autogenstudio/database/migrations/script.py.mako new file mode 100644 index 00000000000..6ce3351093c --- /dev/null +++ b/samples/apps/autogen-studio/autogenstudio/database/migrations/script.py.mako @@ -0,0 +1,27 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +import sqlmodel +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision: str = ${repr(up_revision)} +down_revision: Union[str, None] = ${repr(down_revision)} +branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)} +depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)} + + +def upgrade() -> None: + ${upgrades if upgrades else "pass"} + + +def downgrade() -> None: + ${downgrades if downgrades else "pass"} diff --git a/samples/apps/autogen-studio/autogenstudio/database/utils.py b/samples/apps/autogen-studio/autogenstudio/database/utils.py new file mode 100644 index 00000000000..c14003b414c --- /dev/null +++ b/samples/apps/autogen-studio/autogenstudio/database/utils.py @@ -0,0 +1,323 @@ +# from .util import get_app_root +import os +import time +from datetime import datetime +from pathlib import Path +from typing import Any + +from alembic import command, util +from alembic.config import Config +from loguru import logger + +# from ..utils.db_utils import get_db_uri +from sqlmodel import Session, create_engine, text + +from autogen.agentchat import AssistantAgent + +from ..datamodel import ( + Agent, + AgentConfig, + AgentType, + CodeExecutionConfigTypes, + Model, + Skill, + Workflow, + WorkflowAgentLink, +) + + +def workflow_from_id(workflow_id: int, dbmanager: Any): + workflow = dbmanager.get(Workflow, filters={"id": workflow_id}).data + if not workflow or len(workflow) == 0: + raise ValueError("The specified workflow does not exist.") + workflow = workflow[0].model_dump(mode="json") + workflow_agent_links = dbmanager.get(WorkflowAgentLink, filters={"workflow_id": workflow_id}).data + + def dump_agent(agent: Agent): + exclude = [] + if agent.type != AgentType.groupchat: + exclude = [ + "admin_name", + "messages", + "max_round", + "admin_name", + "speaker_selection_method", + "allow_repeat_speaker", + ] + return agent.model_dump(warnings=False, mode="json", exclude=exclude) + + def get_agent(agent_id): + with Session(dbmanager.engine) as session: + agent: Agent = dbmanager.get_items(Agent, filters={"id": agent_id}, session=session).data[0] + agent_dict = dump_agent(agent) + agent_dict["skills"] = [Skill.model_validate(skill.model_dump(mode="json")) for skill in agent.skills] + model_exclude = [ + "id", + "agent_id", + "created_at", + "updated_at", + "user_id", + "description", + ] + models = [model.model_dump(mode="json", exclude=model_exclude) for model in agent.models] + agent_dict["models"] = [model.model_dump(mode="json") for model in agent.models] + + if len(models) > 0: + agent_dict["config"]["llm_config"] = agent_dict.get("config", {}).get("llm_config", {}) + llm_config = agent_dict["config"]["llm_config"] + if llm_config: + llm_config["config_list"] = models + agent_dict["config"]["llm_config"] = llm_config + agent_dict["agents"] = [get_agent(agent.id) for agent in agent.agents] + return agent_dict + + for link in workflow_agent_links: + agent_dict = get_agent(link.agent_id) + workflow[str(link.agent_type.value)] = agent_dict + return workflow + + +def run_migration(engine_uri: str): + database_dir = Path(__file__).parent + script_location = database_dir / "migrations" + + engine = create_engine(engine_uri) + buffer = open(script_location / "alembic.log", "w") + alembic_cfg = Config(stdout=buffer) + alembic_cfg.set_main_option("script_location", str(script_location)) + alembic_cfg.set_main_option("sqlalchemy.url", engine_uri) + + print(f"Running migrations with engine_uri: {engine_uri}") + + should_initialize_alembic = False + with Session(engine) as session: + try: + session.exec(text("SELECT * FROM alembic_version")) + except Exception: + logger.info("Alembic not initialized") + should_initialize_alembic = True + else: + logger.info("Alembic already initialized") + + if should_initialize_alembic: + try: + logger.info("Initializing alembic") + command.ensure_version(alembic_cfg) + command.upgrade(alembic_cfg, "head") + logger.info("Alembic initialized") + except Exception as exc: + logger.error(f"Error initializing alembic: {exc}") + raise RuntimeError("Error initializing alembic") from exc + + logger.info(f"Running DB migrations in {script_location}") + + try: + buffer.write(f"{datetime.now().isoformat()}: Checking migrations\n") + command.check(alembic_cfg) + except Exception as exc: + if isinstance(exc, (util.exc.CommandError, util.exc.AutogenerateDiffsDetected)): + try: + command.upgrade(alembic_cfg, "head") + time.sleep(3) + except Exception as exc: + logger.error(f"Error running migrations: {exc}") + + try: + buffer.write(f"{datetime.now().isoformat()}: Checking migrations\n") + command.check(alembic_cfg) + except util.exc.AutogenerateDiffsDetected as exc: + logger.info(f"AutogenerateDiffsDetected: {exc}") + # raise RuntimeError( + # f"There's a mismatch between the models and the database.\n{exc}") + except util.exc.CommandError as exc: + logger.error(f"CommandError: {exc}") + # raise RuntimeError(f"Error running migrations: {exc}") + + +def init_db_samples(dbmanager: Any): + workflows = dbmanager.get(Workflow).data + workflow_names = [w.name for w in workflows] + if "Default Workflow" in workflow_names and "Travel Planning Workflow" in workflow_names: + logger.info("Database already initialized with Default and Travel Planning Workflows") + return + logger.info("Initializing database with Default and Travel Planning Workflows") + # models + gpt_4_model = Model( + model="gpt-4-1106-preview", description="OpenAI GPT-4 model", user_id="guestuser@gmail.com", api_type="open_ai" + ) + azure_model = Model( + model="gpt4-turbo", + description="Azure OpenAI model", + user_id="guestuser@gmail.com", + api_type="azure", + base_url="https://api.your azureendpoint.com/v1", + ) + zephyr_model = Model( + model="zephyr", + description="Local Huggingface Zephyr model via vLLM, LMStudio or Ollama", + base_url="http://localhost:1234/v1", + user_id="guestuser@gmail.com", + api_type="open_ai", + ) + + google_gemini_model = Model( + model="gemini-1.5-pro-latest", + description="Google's Gemini model", + user_id="guestuser@gmail.com", + api_type="google", + ) + + # skills + + generate_image_skill = Skill( + name="generate_images", + description="Generate and save images based on a user's query.", + content='\nfrom typing import List\nimport uuid\nimport requests # to perform HTTP requests\nfrom pathlib import Path\n\nfrom openai import OpenAI\n\n\ndef generate_and_save_images(query: str, image_size: str = "1024x1024") -> List[str]:\n """\n Function to paint, draw or illustrate images based on the users query or request. Generates images from a given query using OpenAI\'s DALL-E model and saves them to disk. Use the code below anytime there is a request to create an image.\n\n :param query: A natural language description of the image to be generated.\n :param image_size: The size of the image to be generated. (default is "1024x1024")\n :return: A list of filenames for the saved images.\n """\n\n client = OpenAI() # Initialize the OpenAI client\n response = client.images.generate(model="dall-e-3", prompt=query, n=1, size=image_size) # Generate images\n\n # List to store the file names of saved images\n saved_files = []\n\n # Check if the response is successful\n if response.data:\n for image_data in response.data:\n # Generate a random UUID as the file name\n file_name = str(uuid.uuid4()) + ".png" # Assuming the image is a PNG\n file_path = Path(file_name)\n\n img_url = image_data.url\n img_response = requests.get(img_url)\n if img_response.status_code == 200:\n # Write the binary content to a file\n with open(file_path, "wb") as img_file:\n img_file.write(img_response.content)\n print(f"Image saved to {file_path}")\n saved_files.append(str(file_path))\n else:\n print(f"Failed to download the image from {img_url}")\n else:\n print("No image data found in the response!")\n\n # Return the list of saved files\n return saved_files\n\n\n# Example usage of the function:\n# generate_and_save_images("A cute baby sea otter")\n', + user_id="guestuser@gmail.com", + ) + + # agents + user_proxy_config = AgentConfig( + name="user_proxy", + description="User Proxy Agent Configuration", + human_input_mode="NEVER", + max_consecutive_auto_reply=25, + system_message="You are a helpful assistant", + code_execution_config=CodeExecutionConfigTypes.local, + default_auto_reply="TERMINATE", + llm_config=False, + ) + user_proxy = Agent( + user_id="guestuser@gmail.com", type=AgentType.userproxy, config=user_proxy_config.model_dump(mode="json") + ) + + painter_assistant_config = AgentConfig( + name="default_assistant", + description="Assistant Agent", + human_input_mode="NEVER", + max_consecutive_auto_reply=25, + system_message=AssistantAgent.DEFAULT_SYSTEM_MESSAGE, + code_execution_config=CodeExecutionConfigTypes.none, + llm_config={}, + ) + painter_assistant = Agent( + user_id="guestuser@gmail.com", type=AgentType.assistant, config=painter_assistant_config.model_dump(mode="json") + ) + + planner_assistant_config = AgentConfig( + name="planner_assistant", + description="Assistant Agent", + human_input_mode="NEVER", + max_consecutive_auto_reply=25, + system_message="You are a helpful assistant that can suggest a travel plan for a user. You are the primary cordinator who will receive suggestions or advice from other agents (local_assistant, language_assistant). You must ensure that the finally plan integrates the suggestions from other agents or team members. YOUR FINAL RESPONSE MUST BE THE COMPLETE PLAN. When the plan is complete and all perspectives are integrated, you can respond with TERMINATE.", + code_execution_config=CodeExecutionConfigTypes.none, + llm_config={}, + ) + planner_assistant = Agent( + user_id="guestuser@gmail.com", type=AgentType.assistant, config=planner_assistant_config.model_dump(mode="json") + ) + + local_assistant_config = AgentConfig( + name="local_assistant", + description="Local Assistant Agent", + human_input_mode="NEVER", + max_consecutive_auto_reply=25, + system_message="You are a local assistant that can suggest local activities or places to visit for a user. You can suggest local activities, places to visit, restaurants to eat at, etc. You can also provide information about the weather, local events, etc. You can provide information about the local area, but you cannot suggest a complete travel plan. You can only provide information about the local area.", + code_execution_config=CodeExecutionConfigTypes.none, + llm_config={}, + ) + local_assistant = Agent( + user_id="guestuser@gmail.com", type=AgentType.assistant, config=local_assistant_config.model_dump(mode="json") + ) + + language_assistant_config = AgentConfig( + name="language_assistant", + description="Language Assistant Agent", + human_input_mode="NEVER", + max_consecutive_auto_reply=25, + system_message="You are a helpful assistant that can review travel plans, providing feedback on important/critical tips about how best to address language or communication challenges for the given destination. If the plan already includes language tips, you can mention that the plan is satisfactory, with rationale.", + code_execution_config=CodeExecutionConfigTypes.none, + llm_config={}, + ) + language_assistant = Agent( + user_id="guestuser@gmail.com", + type=AgentType.assistant, + config=language_assistant_config.model_dump(mode="json"), + ) + + # group chat + travel_groupchat_config = AgentConfig( + name="travel_groupchat", + admin_name="groupchat", + description="Group Chat Agent Configuration", + human_input_mode="NEVER", + max_consecutive_auto_reply=25, + system_message="You are a group chat manager", + code_execution_config=CodeExecutionConfigTypes.none, + default_auto_reply="TERMINATE", + llm_config={}, + speaker_selection_method="auto", + ) + travel_groupchat_agent = Agent( + user_id="guestuser@gmail.com", type=AgentType.groupchat, config=travel_groupchat_config.model_dump(mode="json") + ) + + # workflows + default_workflow = Workflow(name="Default Workflow", description="Default workflow", user_id="guestuser@gmail.com") + + travel_workflow = Workflow( + name="Travel Planning Workflow", description="Travel workflow", user_id="guestuser@gmail.com" + ) + + with Session(dbmanager.engine) as session: + session.add(zephyr_model) + session.add(google_gemini_model) + session.add(azure_model) + session.add(gpt_4_model) + session.add(generate_image_skill) + session.add(user_proxy) + session.add(painter_assistant) + session.add(travel_groupchat_agent) + session.add(planner_assistant) + session.add(local_assistant) + session.add(language_assistant) + + session.add(default_workflow) + session.add(travel_workflow) + session.commit() + + dbmanager.link(link_type="agent_model", primary_id=painter_assistant.id, secondary_id=gpt_4_model.id) + dbmanager.link(link_type="agent_skill", primary_id=painter_assistant.id, secondary_id=generate_image_skill.id) + dbmanager.link( + link_type="workflow_agent", primary_id=default_workflow.id, secondary_id=user_proxy.id, agent_type="sender" + ) + dbmanager.link( + link_type="workflow_agent", + primary_id=default_workflow.id, + secondary_id=painter_assistant.id, + agent_type="receiver", + ) + + # link agents to travel groupchat agent + + dbmanager.link(link_type="agent_agent", primary_id=travel_groupchat_agent.id, secondary_id=planner_assistant.id) + dbmanager.link(link_type="agent_agent", primary_id=travel_groupchat_agent.id, secondary_id=local_assistant.id) + dbmanager.link( + link_type="agent_agent", primary_id=travel_groupchat_agent.id, secondary_id=language_assistant.id + ) + dbmanager.link(link_type="agent_agent", primary_id=travel_groupchat_agent.id, secondary_id=user_proxy.id) + dbmanager.link(link_type="agent_model", primary_id=travel_groupchat_agent.id, secondary_id=gpt_4_model.id) + dbmanager.link(link_type="agent_model", primary_id=planner_assistant.id, secondary_id=gpt_4_model.id) + dbmanager.link(link_type="agent_model", primary_id=local_assistant.id, secondary_id=gpt_4_model.id) + dbmanager.link(link_type="agent_model", primary_id=language_assistant.id, secondary_id=gpt_4_model.id) + + dbmanager.link( + link_type="workflow_agent", primary_id=travel_workflow.id, secondary_id=user_proxy.id, agent_type="sender" + ) + dbmanager.link( + link_type="workflow_agent", + primary_id=travel_workflow.id, + secondary_id=travel_groupchat_agent.id, + agent_type="receiver", + ) + logger.info("Successfully initialized database with Default and Travel Planning Workflows") diff --git a/samples/apps/autogen-studio/autogenstudio/datamodel.py b/samples/apps/autogen-studio/autogenstudio/datamodel.py index 8bebdb63ef4..3dbd46c357e 100644 --- a/samples/apps/autogen-studio/autogenstudio/datamodel.py +++ b/samples/apps/autogen-studio/autogenstudio/datamodel.py @@ -1,317 +1,262 @@ -import uuid from datetime import datetime +from enum import Enum from typing import Any, Callable, Dict, List, Literal, Optional, Union -from pydantic.dataclasses import dataclass -from dataclasses import asdict, field - -@dataclass -class Message(object): - user_id: str +from sqlalchemy import ForeignKey, Integer, orm +from sqlmodel import ( + JSON, + Column, + DateTime, + Field, + Relationship, + SQLModel, + func, +) +from sqlmodel import ( + Enum as SqlEnum, +) + +SQLModel.model_config["protected_namespaces"] = () +# pylint: disable=protected-access + + +class Message(SQLModel, table=True): + __table_args__ = {"sqlite_autoincrement": True} + id: Optional[int] = Field(default=None, primary_key=True) + created_at: datetime = Field( + default_factory=datetime.now, + sa_column=Column(DateTime(timezone=True), server_default=func.now()), + ) # pylint: disable=not-callable + updated_at: datetime = Field( + default_factory=datetime.now, + sa_column=Column(DateTime(timezone=True), onupdate=func.now()), + ) # pylint: disable=not-callable + user_id: Optional[str] = None role: str content: str - root_msg_id: Optional[str] = None - msg_id: Optional[str] = None - timestamp: Optional[str] = None - personalize: Optional[bool] = False - ra: Optional[str] = None - code: Optional[str] = None - metadata: Optional[Any] = None - session_id: Optional[str] = None - - def __post_init__(self): - if self.msg_id is None: - self.msg_id = str(uuid.uuid4()) - if self.timestamp is None: - self.timestamp = datetime.now().isoformat() - - def dict(self): - result = asdict(self) - return result - - -@dataclass -class Skill(object): - title: str - content: str - file_name: Optional[str] = None - id: Optional[str] = None - description: Optional[str] = None - timestamp: Optional[str] = None + session_id: Optional[int] = Field( + default=None, sa_column=Column(Integer, ForeignKey("session.id", ondelete="CASCADE")) + ) + connection_id: Optional[str] = None + meta: Optional[Dict] = Field(default={}, sa_column=Column(JSON)) + + +class Session(SQLModel, table=True): + __table_args__ = {"sqlite_autoincrement": True} + id: Optional[int] = Field(default=None, primary_key=True) + created_at: datetime = Field( + default_factory=datetime.now, + sa_column=Column(DateTime(timezone=True), server_default=func.now()), + ) # pylint: disable=not-callable + updated_at: datetime = Field( + default_factory=datetime.now, + sa_column=Column(DateTime(timezone=True), onupdate=func.now()), + ) # pylint: disable=not-callable user_id: Optional[str] = None + workflow_id: Optional[int] = Field(default=None, foreign_key="workflow.id") + name: Optional[str] = None + description: Optional[str] = None - def __post_init__(self): - if self.id is None: - self.id = str(uuid.uuid4()) - if self.timestamp is None: - self.timestamp = datetime.now().isoformat() - if self.user_id is None: - self.user_id = "default" - - def dict(self): - result = asdict(self) - return result +class AgentSkillLink(SQLModel, table=True): + __table_args__ = {"sqlite_autoincrement": True} + agent_id: int = Field(default=None, primary_key=True, foreign_key="agent.id") + skill_id: int = Field(default=None, primary_key=True, foreign_key="skill.id") -# web api data models +class AgentModelLink(SQLModel, table=True): + __table_args__ = {"sqlite_autoincrement": True} + agent_id: int = Field(default=None, primary_key=True, foreign_key="agent.id") + model_id: int = Field(default=None, primary_key=True, foreign_key="model.id") -# autogenflow data models -@dataclass -class Model: - """Data model for Model Config item in LLMConfig for AutoGen""" - model: str - api_key: Optional[str] = None - base_url: Optional[str] = None - api_type: Optional[str] = None - api_version: Optional[str] = None - id: Optional[str] = None - timestamp: Optional[str] = None +class Skill(SQLModel, table=True): + __table_args__ = {"sqlite_autoincrement": True} + id: Optional[int] = Field(default=None, primary_key=True) + created_at: datetime = Field( + default_factory=datetime.now, + sa_column=Column(DateTime(timezone=True), server_default=func.now()), + ) # pylint: disable=not-callable + updated_at: datetime = Field( + default_factory=datetime.now, + sa_column=Column(DateTime(timezone=True), onupdate=func.now()), + ) # pylint: disable=not-callable user_id: Optional[str] = None + name: str + content: str description: Optional[str] = None + secrets: Optional[Dict] = Field(default={}, sa_column=Column(JSON)) + libraries: Optional[Dict] = Field(default={}, sa_column=Column(JSON)) + agents: List["Agent"] = Relationship(back_populates="skills", link_model=AgentSkillLink) - def dict(self): - result = asdict(self) - return result - - def __post_init__(self): - if self.id is None: - self.id = str(uuid.uuid4()) - if self.timestamp is None: - self.timestamp = datetime.now().isoformat() - if self.user_id is None: - self.user_id = "default" - -@dataclass -class LLMConfig: +class LLMConfig(SQLModel, table=False): """Data model for LLM Config for AutoGen""" - config_list: List[Any] = field(default_factory=list) + config_list: List[Any] = Field(default_factory=list) temperature: float = 0 cache_seed: Optional[Union[int, None]] = None timeout: Optional[int] = None - max_tokens: Optional[int] = None + max_tokens: Optional[int] = 1000 extra_body: Optional[dict] = None - def dict(self): - result = asdict(self) - result["config_list"] = [c.dict() for c in self.config_list] - return result +class ModelTypes(str, Enum): + openai = "open_ai" + google = "google" + azure = "azure" -@dataclass -class AgentConfig: - """Data model for Agent Config for AutoGen""" - name: str - llm_config: Optional[Union[LLMConfig, bool]] = False +class Model(SQLModel, table=True): + __table_args__ = {"sqlite_autoincrement": True} + id: Optional[int] = Field(default=None, primary_key=True) + created_at: datetime = Field( + default_factory=datetime.now, + sa_column=Column(DateTime(timezone=True), server_default=func.now()), + ) # pylint: disable=not-callable + updated_at: datetime = Field( + default_factory=datetime.now, + sa_column=Column(DateTime(timezone=True), onupdate=func.now()), + ) # pylint: disable=not-callable + user_id: Optional[str] = None + model: str + api_key: Optional[str] = None + base_url: Optional[str] = None + api_type: ModelTypes = Field(default=ModelTypes.openai, sa_column=Column(SqlEnum(ModelTypes))) + api_version: Optional[str] = None + description: Optional[str] = None + agents: List["Agent"] = Relationship(back_populates="models", link_model=AgentModelLink) + + +class CodeExecutionConfigTypes(str, Enum): + local = "local" + docker = "docker" + none = "none" + + +class AgentConfig(SQLModel, table=False): + name: Optional[str] = None human_input_mode: str = "NEVER" max_consecutive_auto_reply: int = 10 system_message: Optional[str] = None is_termination_msg: Optional[Union[bool, str, Callable]] = None - code_execution_config: Optional[Union[bool, str, Dict[str, Any]]] = None + code_execution_config: CodeExecutionConfigTypes = Field( + default=CodeExecutionConfigTypes.local, sa_column=Column(SqlEnum(CodeExecutionConfigTypes)) + ) default_auto_reply: Optional[str] = "" description: Optional[str] = None + llm_config: Optional[Union[LLMConfig, bool]] = Field(default=False, sa_column=Column(JSON)) - def dict(self): - result = asdict(self) - if isinstance(result["llm_config"], LLMConfig): - result["llm_config"] = result["llm_config"].dict() - return result - - -@dataclass -class AgentFlowSpec: - """Data model to help flow load agents from config""" - - type: Literal["assistant", "userproxy"] - config: AgentConfig - id: Optional[str] = None - timestamp: Optional[str] = None - user_id: Optional[str] = None - skills: Optional[Union[None, List[Skill]]] = None - - def __post_init__(self): - if self.timestamp is None: - self.timestamp = datetime.now().isoformat() - if self.id is None: - self.id = str(uuid.uuid4()) - if self.user_id is None: - self.user_id = "default" - - def dict(self): - result = asdict(self) - return result - - -@dataclass -class GroupChatConfig: - """Data model for GroupChat Config for AutoGen""" - - agents: List[AgentFlowSpec] = field(default_factory=list) - admin_name: str = "Admin" - messages: List[Dict] = field(default_factory=list) - max_round: Optional[int] = 10 admin_name: Optional[str] = "Admin" + messages: Optional[List[Dict]] = Field(default_factory=list) + max_round: Optional[int] = 100 speaker_selection_method: Optional[str] = "auto" - # TODO: match the new group chat default and support transition spec - allow_repeat_speaker: Optional[Union[bool, List[AgentConfig]]] = True + allow_repeat_speaker: Optional[Union[bool, List["AgentConfig"]]] = True - def dict(self): - result = asdict(self) - result["agents"] = [a.dict() for a in self.agents] - return result +class AgentType(str, Enum): + assistant = "assistant" + userproxy = "userproxy" + groupchat = "groupchat" -@dataclass -class GroupChatFlowSpec: - """Data model to help flow load agents from config""" - type: Literal["groupchat"] - config: AgentConfig = field(default_factory=AgentConfig) - groupchat_config: Optional[GroupChatConfig] = field(default_factory=GroupChatConfig) - id: Optional[str] = None - timestamp: Optional[str] = None - user_id: Optional[str] = None - skills: Optional[Union[None, List[Skill]]] = None +class WorkflowAgentType(str, Enum): + sender = "sender" + receiver = "receiver" + planner = "planner" + - def __post_init__(self): - if self.timestamp is None: - self.timestamp = datetime.now().isoformat() - if self.id is None: - self.id = str(uuid.uuid4()) - if self.user_id is None: - self.user_id = "default" +class WorkflowAgentLink(SQLModel, table=True): + __table_args__ = {"sqlite_autoincrement": True} + workflow_id: int = Field(default=None, primary_key=True, foreign_key="workflow.id") + agent_id: int = Field(default=None, primary_key=True, foreign_key="agent.id") + agent_type: WorkflowAgentType = Field( + default=WorkflowAgentType.sender, + sa_column=Column(SqlEnum(WorkflowAgentType), primary_key=True), + ) - def dict(self): - result = asdict(self) - # result["config"] = self.config.dict() - # result["groupchat_config"] = self.groupchat_config.dict() - return result +class AgentLink(SQLModel, table=True): + __table_args__ = {"sqlite_autoincrement": True} + parent_id: Optional[int] = Field(default=None, foreign_key="agent.id", primary_key=True) + agent_id: Optional[int] = Field(default=None, foreign_key="agent.id", primary_key=True) -@dataclass -class AgentWorkFlowConfig: - """Data model for Flow Config for AutoGen""" +class Agent(SQLModel, table=True): + __table_args__ = {"sqlite_autoincrement": True} + id: Optional[int] = Field(default=None, primary_key=True) + created_at: datetime = Field( + default_factory=datetime.now, + sa_column=Column(DateTime(timezone=True), server_default=func.now()), + ) # pylint: disable=not-callable + updated_at: datetime = Field( + default_factory=datetime.now, + sa_column=Column(DateTime(timezone=True), onupdate=func.now()), + ) # pylint: disable=not-callable + user_id: Optional[str] = None + type: AgentType = Field(default=AgentType.assistant, sa_column=Column(SqlEnum(AgentType))) + config: AgentConfig = Field(default_factory=AgentConfig, sa_column=Column(JSON)) + skills: List[Skill] = Relationship(back_populates="agents", link_model=AgentSkillLink) + models: List[Model] = Relationship(back_populates="agents", link_model=AgentModelLink) + workflows: List["Workflow"] = Relationship(link_model=WorkflowAgentLink, back_populates="agents") + parents: List["Agent"] = Relationship( + back_populates="agents", + link_model=AgentLink, + sa_relationship_kwargs=dict( + primaryjoin="Agent.id==AgentLink.agent_id", + secondaryjoin="Agent.id==AgentLink.parent_id", + ), + ) + agents: List["Agent"] = Relationship( + back_populates="parents", + link_model=AgentLink, + sa_relationship_kwargs=dict( + primaryjoin="Agent.id==AgentLink.parent_id", + secondaryjoin="Agent.id==AgentLink.agent_id", + ), + ) + + +class WorkFlowType(str, Enum): + twoagents = "twoagents" + groupchat = "groupchat" + + +class WorkFlowSummaryMethod(str, Enum): + last = "last" + none = "none" + llm = "llm" + + +class Workflow(SQLModel, table=True): + __table_args__ = {"sqlite_autoincrement": True} + id: Optional[int] = Field(default=None, primary_key=True) + created_at: datetime = Field( + default_factory=datetime.now, + sa_column=Column(DateTime(timezone=True), server_default=func.now()), + ) # pylint: disable=not-callable + updated_at: datetime = Field( + default_factory=datetime.now, + sa_column=Column(DateTime(timezone=True), onupdate=func.now()), + ) # pylint: disable=not-callable + user_id: Optional[str] = None name: str description: str - sender: AgentFlowSpec - receiver: Union[AgentFlowSpec, GroupChatFlowSpec] - type: Literal["twoagents", "groupchat"] = "twoagents" - id: Optional[str] = None - user_id: Optional[str] = None - timestamp: Optional[str] = None - # how the agent message summary is generated. last: only last message is used, none: no summary, llm: use llm to generate summary - summary_method: Optional[Literal["last", "none", "llm"]] = "last" - - def init_spec(self, spec: Dict): - """initialize the agent spec""" - if not isinstance(spec, dict): - spec = spec.dict() - if spec["type"] == "groupchat": - return GroupChatFlowSpec(**spec) - else: - return AgentFlowSpec(**spec) - - def __post_init__(self): - if self.id is None: - self.id = str(uuid.uuid4()) - self.sender = self.init_spec(self.sender) - self.receiver = self.init_spec(self.receiver) - if self.user_id is None: - self.user_id = "default" - if self.timestamp is None: - self.timestamp = datetime.now().isoformat() - - def dict(self): - result = asdict(self) - result["sender"] = self.sender.dict() - result["receiver"] = self.receiver.dict() - return result - - -@dataclass -class Session(object): - """Data model for AutoGen Chat Session""" - - user_id: str - id: Optional[str] = None - timestamp: Optional[str] = None - flow_config: AgentWorkFlowConfig = None - name: Optional[str] = None - description: Optional[str] = None + agents: List[Agent] = Relationship(back_populates="workflows", link_model=WorkflowAgentLink) + type: WorkFlowType = Field(default=WorkFlowType.twoagents, sa_column=Column(SqlEnum(WorkFlowType))) + summary_method: Optional[WorkFlowSummaryMethod] = Field( + default=WorkFlowSummaryMethod.last, + sa_column=Column(SqlEnum(WorkFlowSummaryMethod)), + ) - def __post_init__(self): - if self.timestamp is None: - self.timestamp = datetime.now().isoformat() - if self.id is None: - self.id = str(uuid.uuid4()) - - def dict(self): - result = asdict(self) - result["flow_config"] = self.flow_config.dict() - return result - - -@dataclass -class Gallery(object): - """Data model for Gallery Item""" - - session: Session - messages: List[Message] - tags: List[str] - id: Optional[str] = None - timestamp: Optional[str] = None - - def __post_init__(self): - if self.timestamp is None: - self.timestamp = datetime.now().isoformat() - if self.id is None: - self.id = str(uuid.uuid4()) - - def dict(self): - result = asdict(self) - return result - - -@dataclass -class ChatWebRequestModel(object): - """Data model for Chat Web Request for Web End""" - - message: Message - flow_config: AgentWorkFlowConfig - - -@dataclass -class DeleteMessageWebRequestModel(object): - user_id: str - msg_id: str - session_id: Optional[str] = None - - -@dataclass -class DBWebRequestModel(object): - user_id: str - msg_id: Optional[str] = None - session: Optional[Session] = None - skill: Optional[Skill] = None - tags: Optional[List[str]] = None - agent: Optional[AgentFlowSpec] = None - workflow: Optional[AgentWorkFlowConfig] = None - model: Optional[Model] = None - message: Optional[Message] = None - connection_id: Optional[str] = None +class Response(SQLModel): + message: str + status: bool + data: Optional[Any] = None -@dataclass -class SocketMessage(object): + +class SocketMessage(SQLModel, table=False): connection_id: str data: Dict[str, Any] type: str - - def dict(self): - result = asdict(self) - return result diff --git a/samples/apps/autogen-studio/autogenstudio/utils/__init__.py b/samples/apps/autogen-studio/autogenstudio/utils/__init__.py index f37b0b0486a..16281fe0b66 100644 --- a/samples/apps/autogen-studio/autogenstudio/utils/__init__.py +++ b/samples/apps/autogen-studio/autogenstudio/utils/__init__.py @@ -1,2 +1 @@ -from .dbutils import * from .utils import * diff --git a/samples/apps/autogen-studio/autogenstudio/utils/dbutils.py b/samples/apps/autogen-studio/autogenstudio/utils/dbutils.py deleted file mode 100644 index a7c4b7d4c05..00000000000 --- a/samples/apps/autogen-studio/autogenstudio/utils/dbutils.py +++ /dev/null @@ -1,860 +0,0 @@ -import json -import logging -import sqlite3 -import threading -import os -from typing import Any, List, Dict, Optional, Tuple -from ..datamodel import AgentFlowSpec, AgentWorkFlowConfig, Gallery, Message, Model, Session, Skill -from ..version import __version__ as __db_version__ - - -VERSION_TABLE_SQL = """ - CREATE TABLE IF NOT EXISTS version ( - - version TEXT NOT NULL, - UNIQUE (version) - ) - """ - -MODELS_TABLE_SQL = """ - CREATE TABLE IF NOT EXISTS models ( - id TEXT NOT NULL, - user_id TEXT NOT NULL, - timestamp DATETIME NOT NULL, - model TEXT, - api_key TEXT, - base_url TEXT, - api_type TEXT, - api_version TEXT, - description TEXT, - UNIQUE (id, user_id) - ) - """ - - -MESSAGES_TABLE_SQL = """ - CREATE TABLE IF NOT EXISTS messages ( - user_id TEXT NOT NULL, - session_id TEXT, - root_msg_id TEXT NOT NULL, - msg_id TEXT, - role TEXT NOT NULL, - content TEXT NOT NULL, - metadata TEXT, - timestamp DATETIME, - UNIQUE (user_id, root_msg_id, msg_id) - ) - """ - -SESSIONS_TABLE_SQL = """ - CREATE TABLE IF NOT EXISTS sessions ( - id TEXT NOT NULL, - user_id TEXT NOT NULL, - timestamp DATETIME NOT NULL, - name TEXT, - flow_config TEXT, - UNIQUE (user_id, id) - ) - """ - -SKILLS_TABLE_SQL = """ - CREATE TABLE IF NOT EXISTS skills ( - id TEXT NOT NULL, - user_id TEXT NOT NULL, - timestamp DATETIME NOT NULL, - content TEXT, - title TEXT, - file_name TEXT, - UNIQUE (id, user_id) - ) - """ -AGENTS_TABLE_SQL = """ - CREATE TABLE IF NOT EXISTS agents ( - - id TEXT NOT NULL, - user_id TEXT NOT NULL, - timestamp DATETIME NOT NULL, - config TEXT, - type TEXT, - skills TEXT, - UNIQUE (id, user_id) - ) - """ - -WORKFLOWS_TABLE_SQL = """ - CREATE TABLE IF NOT EXISTS workflows ( - id TEXT NOT NULL, - user_id TEXT NOT NULL, - timestamp DATETIME NOT NULL, - sender TEXT, - receiver TEXT, - type TEXT, - name TEXT, - description TEXT, - summary_method TEXT, - UNIQUE (id, user_id) - ) - """ - -GALLERY_TABLE_SQL = """ - CREATE TABLE IF NOT EXISTS gallery ( - id TEXT NOT NULL, - session TEXT, - messages TEXT, - tags TEXT, - timestamp DATETIME NOT NULL, - UNIQUE ( id) - ) - """ - - -lock = threading.Lock() -logger = logging.getLogger() - - -class DBManager: - """ - A database manager class that handles the creation and interaction with an SQLite database. - """ - - def __init__(self, path: str = "database.sqlite", **kwargs: Any) -> None: - """ - Initializes the DBManager object, creates a database if it does not exist, and establishes a connection. - - Args: - path (str): The file path to the SQLite database file. - **kwargs: Additional keyword arguments to pass to the sqlite3.connect method. - """ - - self.path = path - # check if the database exists, if not create it - # self.reset_db() - if not os.path.exists(self.path): - logger.info("Creating database") - self.init_db(path=self.path, **kwargs) - - try: - self.conn = sqlite3.connect(self.path, check_same_thread=False, **kwargs) - self.cursor = self.conn.cursor() - self.migrate() - except Exception as e: - logger.error("Error connecting to database: %s", e) - raise e - - def migrate(self): - """ - Run migrations to update the database schema. - """ - self.add_column_if_not_exists("sessions", "name", "TEXT") - self.add_column_if_not_exists("models", "description", "TEXT") - - def add_column_if_not_exists(self, table: str, column: str, column_type: str): - """ - Adds a new column to the specified table if it does not exist. - - Args: - table (str): The table name where the column should be added. - column (str): The column name that should be added. - column_type (str): The data type of the new column. - """ - try: - self.cursor.execute(f"PRAGMA table_info({table})") - column_names = [row[1] for row in self.cursor.fetchall()] - if column not in column_names: - self.cursor.execute(f"ALTER TABLE {table} ADD COLUMN {column} {column_type}") - self.conn.commit() - logger.info(f"Migration: New '{column}' column has been added to the '{table}' table.") - else: - logger.info(f"'{column}' column already exists in the '{table}' table.") - - except Exception as e: - print(f"Error while checking and updating '{table}' table: {e}") - - def reset_db(self): - """ - Reset the database by deleting the database file and creating a new one. - """ - print("resetting db") - if os.path.exists(self.path): - os.remove(self.path) - self.init_db(path=self.path) - - def init_db(self, path: str = "database.sqlite", **kwargs: Any) -> None: - """ - Initializes the database by creating necessary tables. - - Args: - path (str): The file path to the SQLite database file. - **kwargs: Additional keyword arguments to pass to the sqlite3.connect method. - """ - # Connect to the database (or create a new one if it doesn't exist) - self.conn = sqlite3.connect(path, check_same_thread=False, **kwargs) - self.cursor = self.conn.cursor() - - # Create the version table - self.cursor.execute(VERSION_TABLE_SQL) - self.cursor.execute("INSERT INTO version (version) VALUES (?)", (__db_version__,)) - - # Create the models table - self.cursor.execute(MODELS_TABLE_SQL) - - # Create the messages table - self.cursor.execute(MESSAGES_TABLE_SQL) - - # Create a sessions table - self.cursor.execute(SESSIONS_TABLE_SQL) - - # Create a skills - self.cursor.execute(SKILLS_TABLE_SQL) - - # Create a gallery table - self.cursor.execute(GALLERY_TABLE_SQL) - - # Create a agents table - self.cursor.execute(AGENTS_TABLE_SQL) - - # Create a workflows table - self.cursor.execute(WORKFLOWS_TABLE_SQL) - - # init skills table with content of defaultskills.json in current directory - current_dir = os.path.dirname(os.path.realpath(__file__)) - with open(os.path.join(current_dir, "dbdefaults.json"), "r", encoding="utf-8") as json_file: - data = json.load(json_file) - skills = data["skills"] - agents = data["agents"] - models = data["models"] - for model in models: - model = Model(**model) - self.cursor.execute( - "INSERT INTO models (id, user_id, timestamp, model, api_key, base_url, api_type, api_version, description) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", - ( - model.id, - "default", - model.timestamp, - model.model, - model.api_key, - model.base_url, - model.api_type, - model.api_version, - model.description, - ), - ) - - for skill in skills: - skill = Skill(**skill) - - self.cursor.execute( - "INSERT INTO skills (id, user_id, timestamp, content, title, file_name) VALUES (?, ?, ?, ?, ?, ?)", - (skill.id, "default", skill.timestamp, skill.content, skill.title, skill.file_name), - ) - for agent in agents: - agent = AgentFlowSpec(**agent) - agent.skills = [skill.dict() for skill in agent.skills] if agent.skills else None - self.cursor.execute( - "INSERT INTO agents (id, user_id, timestamp, config, type, skills) VALUES (?, ?, ?, ?, ?, ?)", - ( - agent.id, - "default", - agent.timestamp, - json.dumps(agent.config.dict()), - agent.type, - json.dumps(agent.skills), - ), - ) - - for workflow in data["workflows"]: - workflow = AgentWorkFlowConfig(**workflow) - self.cursor.execute( - "INSERT INTO workflows (id, user_id, timestamp, sender, receiver, type, name, description, summary_method) VALUES (?, ?, ?, ?, ?, ?, ?, ?,?)", - ( - workflow.id, - "default", - workflow.timestamp, - json.dumps(workflow.sender.dict()), - json.dumps(workflow.receiver.dict()), - workflow.type, - workflow.name, - workflow.description, - workflow.summary_method, - ), - ) - - # Commit the changes and close the connection - self.conn.commit() - - def query(self, query: str, args: Tuple = (), return_json: bool = False) -> List[Dict[str, Any]]: - """ - Executes a given SQL query and returns the results. - - Args: - query (str): The SQL query to execute. - args (Tuple): The arguments to pass to the SQL query. - return_json (bool): If True, the results will be returned as a list of dictionaries. - - Returns: - List[Dict[str, Any]]: The result of the SQL query. - """ - try: - with lock: - self.cursor.execute(query, args) - result = self.cursor.fetchall() - self.commit() - if return_json: - result = [dict(zip([key[0] for key in self.cursor.description], row)) for row in result] - return result - except Exception as e: - logger.error("Error running query with query %s and args %s: %s", query, args, e) - raise e - - def commit(self) -> None: - """ - Commits the current transaction Modelto the database. - """ - self.conn.commit() - - def close(self) -> None: - """ - Closes the database connection. - """ - self.conn.close() - - -def get_models(user_id: str, dbmanager: DBManager) -> List[dict]: - """ - Get all models for a given user from the database. - - Args: - user_id: The user id to get models for - dbmanager: The DBManager instance to interact with the database - - Returns: - A list of model configurations - """ - query = "SELECT * FROM models WHERE user_id = ? OR user_id = ?" - args = (user_id, "default") - results = dbmanager.query(query, args, return_json=True) - return results - - -def upsert_model(model: Model, dbmanager: DBManager) -> List[dict]: - """ - Insert or update a model configuration in the database. - - Args: - model: The Model object containing model configuration data - dbmanager: The DBManager instance to interact with the database - - Returns: - A list of model configurations - """ - - # Check if the model config with the provided id already exists in the database - existing_model = get_item_by_field("models", "id", model.id, dbmanager) - - if existing_model: - # If the model config exists, update it with the new data - updated_data = { - "model": model.model, - "api_key": model.api_key, - "base_url": model.base_url, - "api_type": model.api_type, - "api_version": model.api_version, - "user_id": model.user_id, - "timestamp": model.timestamp, - "description": model.description, - } - update_item("models", model.id, updated_data, dbmanager) - else: - # If the model config does not exist, insert a new one - query = """ - INSERT INTO models (id, user_id, timestamp, model, api_key, base_url, api_type, api_version, description) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) - """ - args = ( - model.id, - model.user_id, - model.timestamp, - model.model, - model.api_key, - model.base_url, - model.api_type, - model.api_version, - model.description, - ) - dbmanager.query(query=query, args=args) - - # Return the inserted or updated model config - models = get_models(model.user_id, dbmanager) - return models - - -def delete_model(model: Model, dbmanager: DBManager) -> List[dict]: - """ - Delete a model configuration from the database where id = model.id and user_id = model.user_id. - - Args: - model: The Model object containing model configuration data - dbmanager: The DBManager instance to interact with the database - - Returns: - A list of model configurations - """ - - query = "DELETE FROM models WHERE id = ? AND user_id = ?" - args = (model.id, model.user_id) - dbmanager.query(query=query, args=args) - - # Return the remaining model configs - models = get_models(model.user_id, dbmanager) - return models - - -def create_message(message: Message, dbmanager: DBManager) -> List[dict]: - """ - Save a message in the database using the provided database manager. - - :param message: The Message object containing message data - :param dbmanager: The DBManager instance used to interact with the database - """ - query = "INSERT INTO messages (user_id, root_msg_id, msg_id, role, content, metadata, timestamp, session_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)" - args = ( - message.user_id, - message.root_msg_id, - message.msg_id, - message.role, - message.content, - message.metadata, - message.timestamp, - message.session_id, - ) - dbmanager.query(query=query, args=args) - messages = get_messages(user_id=message.user_id, session_id=message.session_id, dbmanager=dbmanager) - return messages - - -def get_messages(user_id: str, session_id: str, dbmanager: DBManager) -> List[dict]: - """ - Load messages for a specific user and session from the database, sorted by timestamp. - - :param user_id: The ID of the user whose messages are to be loaded - :param session_id: The ID of the session whose messages are to be loaded - :param dbmanager: The DBManager instance to interact with the database - - :return: A list of dictionaries, each representing a message - """ - query = "SELECT * FROM messages WHERE user_id = ? AND session_id = ?" - args = (user_id, session_id) - result = dbmanager.query(query=query, args=args, return_json=True) - # Sort by timestamp ascending - result = sorted(result, key=lambda k: k["timestamp"], reverse=False) - return result - - -def get_sessions(user_id: str, dbmanager: DBManager) -> List[dict]: - """ - Load sessions for a specific user from the database, sorted by timestamp. - - :param user_id: The ID of the user whose sessions are to be loaded - :param dbmanager: The DBManager instance to interact with the database - :return: A list of dictionaries, each representing a session - """ - query = "SELECT * FROM sessions WHERE user_id = ?" - args = (user_id,) - result = dbmanager.query(query=query, args=args, return_json=True) - # Sort by timestamp ascending - result = sorted(result, key=lambda k: k["timestamp"], reverse=True) - for row in result: - row["flow_config"] = json.loads(row["flow_config"]) - return result - - -def create_session(user_id: str, session: Session, dbmanager: DBManager) -> List[dict]: - """ - Create a new session for a specific user in the database. - - :param user_id: The ID of the user whose session is to be created - :param dbmanager: The DBManager instance to interact with the database - :return: A list of dictionaries, each representing a session - """ - query = "INSERT INTO sessions (user_id, id, timestamp, flow_config) VALUES (?, ?, ?,?)" - args = (session.user_id, session.id, session.timestamp, json.dumps(session.flow_config.dict())) - dbmanager.query(query=query, args=args) - sessions = get_sessions(user_id=user_id, dbmanager=dbmanager) - - return sessions - - -def rename_session(name: str, session: Session, dbmanager: DBManager) -> List[dict]: - """ - Edit a session for a specific user in the database. - - :param name: The new name of the session - :param session: The Session object containing session data - :param dbmanager: The DBManager instance to interact with the database - :return: A list of dictionaries, each representing a session - """ - - query = "UPDATE sessions SET name = ? WHERE id = ?" - args = (name, session.id) - dbmanager.query(query=query, args=args) - sessions = get_sessions(user_id=session.user_id, dbmanager=dbmanager) - - return sessions - - -def delete_session(session: Session, dbmanager: DBManager) -> List[dict]: - """ - Delete a specific session and all messages for that session in the database. - - :param session: The Session object containing session data - :param dbmanager: The DBManager instance to interact with the database - :return: A list of the remaining sessions - """ - - query = "DELETE FROM sessions WHERE id = ?" - args = (session.id,) - dbmanager.query(query=query, args=args) - - query = "DELETE FROM messages WHERE session_id = ?" - args = (session.id,) - dbmanager.query(query=query, args=args) - - return get_sessions(user_id=session.user_id, dbmanager=dbmanager) - - -def create_gallery(session: Session, dbmanager: DBManager, tags: List[str] = []) -> Gallery: - """ - Publish a session to the gallery table in the database. Fetches the session messages first, then saves session and messages object to the gallery database table. - :param session: The Session object containing session data - :param dbmanager: The DBManager instance used to interact with the database - :param tags: A list of tags to associate with the session - :return: A gallery object containing the session and messages objects - """ - - messages = get_messages(user_id=session.user_id, session_id=session.id, dbmanager=dbmanager) - gallery_item = Gallery(session=session, messages=messages, tags=tags) - query = "INSERT INTO gallery (id, session, messages, tags, timestamp) VALUES (?, ?, ?, ?,?)" - args = ( - gallery_item.id, - json.dumps(gallery_item.session.dict()), - json.dumps([message.dict() for message in gallery_item.messages]), - json.dumps(gallery_item.tags), - gallery_item.timestamp, - ) - dbmanager.query(query=query, args=args) - return gallery_item - - -def get_gallery(gallery_id, dbmanager: DBManager) -> List[Gallery]: - """ - Load gallery items from the database, sorted by timestamp. If gallery_id is provided, only the gallery item with the matching gallery_id will be returned. - - :param gallery_id: The ID of the gallery item to be loaded - :param dbmanager: The DBManager instance to interact with the database - :return: A list of Gallery objects - """ - - if gallery_id: - query = "SELECT * FROM gallery WHERE id = ?" - args = (gallery_id,) - else: - query = "SELECT * FROM gallery" - args = () - result = dbmanager.query(query=query, args=args, return_json=True) - # Sort by timestamp ascending - result = sorted(result, key=lambda k: k["timestamp"], reverse=True) - gallery = [] - for row in result: - gallery_item = Gallery( - id=row["id"], - session=Session(**json.loads(row["session"])), - messages=[Message(**message) for message in json.loads(row["messages"])], - tags=json.loads(row["tags"]), - timestamp=row["timestamp"], - ) - gallery.append(gallery_item) - return gallery - - -def get_skills(user_id: str, dbmanager: DBManager) -> List[Skill]: - """ - Load skills from the database, sorted by timestamp. Load skills where id = user_id or user_id = default. - - :param user_id: The ID of the user whose skills are to be loaded - :param dbmanager: The DBManager instance to interact with the database - :return: A list of Skill objects - """ - - query = "SELECT * FROM skills WHERE user_id = ? OR user_id = ?" - args = (user_id, "default") - result = dbmanager.query(query=query, args=args, return_json=True) - # Sort by timestamp ascending - result = sorted(result, key=lambda k: k["timestamp"], reverse=True) - skills = [] - for row in result: - skill = Skill(**row) - skills.append(skill) - return skills - - -def upsert_skill(skill: Skill, dbmanager: DBManager) -> List[Skill]: - """ - Insert or update a skill for a specific user in the database. - - If the skill with the given ID already exists, it will be updated with the new data. - Otherwise, a new skill will be created. - - :param skill: The Skill object containing skill data - :param dbmanager: The DBManager instance to interact with the database - :return: A list of dictionaries, each representing a skill - """ - - existing_skill = get_item_by_field("skills", "id", skill.id, dbmanager) - - if existing_skill: - updated_data = { - "user_id": skill.user_id, - "timestamp": skill.timestamp, - "content": skill.content, - "title": skill.title, - "file_name": skill.file_name, - } - update_item("skills", skill.id, updated_data, dbmanager) - else: - query = "INSERT INTO skills (id, user_id, timestamp, content, title, file_name) VALUES (?, ?, ?, ?, ?, ?)" - args = (skill.id, skill.user_id, skill.timestamp, skill.content, skill.title, skill.file_name) - dbmanager.query(query=query, args=args) - - skills = get_skills(user_id=skill.user_id, dbmanager=dbmanager) - - return skills - - -def delete_skill(skill: Skill, dbmanager: DBManager) -> List[Skill]: - """ - Delete a skill for a specific user in the database. - - :param skill: The Skill object containing skill data - :param dbmanager: The DBManager instance to interact with the database - :return: A list of dictionaries, each representing a skill - """ - # delete where id = skill.id and user_id = skill.user_id - query = "DELETE FROM skills WHERE id = ? AND user_id = ?" - args = (skill.id, skill.user_id) - dbmanager.query(query=query, args=args) - - return get_skills(user_id=skill.user_id, dbmanager=dbmanager) - - -def delete_message( - user_id: str, msg_id: str, session_id: str, dbmanager: DBManager, delete_all: bool = False -) -> List[dict]: - """ - Delete a specific message or all messages for a user and session from the database. - - :param user_id: The ID of the user whose messages are to be deleted - :param msg_id: The ID of the specific message to be deleted (ignored if delete_all is True) - :param session_id: The ID of the session whose messages are to be deleted - :param dbmanager: The DBManager instance to interact with the database - :param delete_all: If True, all messages for the user will be deleted - :return: A list of the remaining messages if not all were deleted, otherwise an empty list - """ - - if delete_all: - query = "DELETE FROM messages WHERE user_id = ? AND session_id = ?" - args = (user_id, session_id) - dbmanager.query(query=query, args=args) - return [] - else: - query = "DELETE FROM messages WHERE user_id = ? AND msg_id = ? AND session_id = ?" - args = (user_id, msg_id, session_id) - dbmanager.query(query=query, args=args) - messages = get_messages(user_id=user_id, session_id=session_id, dbmanager=dbmanager) - return messages - - -def get_agents(user_id: str, dbmanager: DBManager) -> List[AgentFlowSpec]: - """ - Load agents from the database, sorted by timestamp. Load agents where id = user_id or user_id = default. - - :param user_id: The ID of the user whose agents are to be loaded - :param dbmanager: The DBManager instance to interact with the database - :return: A list of AgentFlowSpec objects - """ - - query = "SELECT * FROM agents WHERE user_id = ? OR user_id = ?" - args = (user_id, "default") - result = dbmanager.query(query=query, args=args, return_json=True) - # Sort by timestamp ascending - result = sorted(result, key=lambda k: k["timestamp"], reverse=True) - agents = [] - for row in result: - row["config"] = json.loads(row["config"]) - row["skills"] = json.loads(row["skills"] or "[]") - agent = AgentFlowSpec(**row) - agents.append(agent) - return agents - - -def upsert_agent(agent_flow_spec: AgentFlowSpec, dbmanager: DBManager) -> List[Dict[str, Any]]: - """ - Insert or update an agent for a specific user in the database. - - If the agent with the given ID already exists, it will be updated with the new data. - Otherwise, a new agent will be created. - - :param agent_flow_spec: The AgentFlowSpec object containing agent configuration - :param dbmanager: The DBManager instance to interact with the database - :return: A list of dictionaries, each representing an agent after insertion or update - """ - - existing_agent = get_item_by_field("agents", "id", agent_flow_spec.id, dbmanager) - - if existing_agent: - updated_data = { - "user_id": agent_flow_spec.user_id, - "timestamp": agent_flow_spec.timestamp, - "config": json.dumps(agent_flow_spec.config.dict()), - "type": agent_flow_spec.type, - "skills": json.dumps([x.dict() for x in agent_flow_spec.skills] if agent_flow_spec.skills else []), - } - update_item("agents", agent_flow_spec.id, updated_data, dbmanager) - else: - query = "INSERT INTO agents (id, user_id, timestamp, config, type, skills) VALUES (?, ?, ?, ?, ?,?)" - config_json = json.dumps(agent_flow_spec.config.dict()) - args = ( - agent_flow_spec.id, - agent_flow_spec.user_id, - agent_flow_spec.timestamp, - config_json, - agent_flow_spec.type, - json.dumps([x.dict() for x in agent_flow_spec.skills] if agent_flow_spec.skills else []), - ) - dbmanager.query(query=query, args=args) - - agents = get_agents(user_id=agent_flow_spec.user_id, dbmanager=dbmanager) - return agents - - -def delete_agent(agent: AgentFlowSpec, dbmanager: DBManager) -> List[Dict[str, Any]]: - """ - Delete an agent for a specific user from the database. - - :param agent: The AgentFlowSpec object containing agent configuration - :param dbmanager: The DBManager instance to interact with the database - :return: A list of dictionaries, each representing an agent after deletion - """ - - # delete based on agent.id and agent.user_id - query = "DELETE FROM agents WHERE id = ? AND user_id = ?" - args = (agent.id, agent.user_id) - dbmanager.query(query=query, args=args) - - return get_agents(user_id=agent.user_id, dbmanager=dbmanager) - - -def get_item_by_field(table: str, field: str, value: Any, dbmanager: DBManager) -> Optional[Dict[str, Any]]: - query = f"SELECT * FROM {table} WHERE {field} = ?" - args = (value,) - result = dbmanager.query(query=query, args=args) - return result[0] if result else None - - -def update_item(table: str, item_id: str, updated_data: Dict[str, Any], dbmanager: DBManager) -> None: - set_clause = ", ".join([f"{key} = ?" for key in updated_data.keys()]) - query = f"UPDATE {table} SET {set_clause} WHERE id = ?" - args = (*updated_data.values(), item_id) - dbmanager.query(query=query, args=args) - - -def get_workflows(user_id: str, dbmanager: DBManager) -> List[Dict[str, Any]]: - """ - Load workflows for a specific user from the database, sorted by timestamp. - - :param user_id: The ID of the user whose workflows are to be loaded - :param dbmanager: The DBManager instance to interact with the database - :return: A list of dictionaries, each representing a workflow - """ - query = "SELECT * FROM workflows WHERE user_id = ? OR user_id = ?" - args = (user_id, "default") - result = dbmanager.query(query=query, args=args, return_json=True) - # Sort by timestamp ascending - result = sorted(result, key=lambda k: k["timestamp"], reverse=True) - workflows = [] - for row in result: - row["sender"] = json.loads(row["sender"]) - row["receiver"] = json.loads(row["receiver"]) - workflow = AgentWorkFlowConfig(**row) - workflows.append(workflow) - return workflows - - -def upsert_workflow(workflow: AgentWorkFlowConfig, dbmanager: DBManager) -> List[Dict[str, Any]]: - """ - Insert or update a workflow for a specific user in the database. - - If the workflow with the given ID already exists, it will be updated with the new data. - Otherwise, a new workflow will be created. - - :param workflow: The AgentWorkFlowConfig object containing workflow data - :param dbmanager: The DBManager instance to interact with the database - :return: A list of dictionaries, each representing a workflow after insertion or update - """ - existing_workflow = get_item_by_field("workflows", "id", workflow.id, dbmanager) - - # print(workflow.receiver) - - if existing_workflow: - updated_data = { - "user_id": workflow.user_id, - "timestamp": workflow.timestamp, - "sender": json.dumps(workflow.sender.dict()), - "receiver": json.dumps( - [receiver.dict() for receiver in workflow.receiver] - if isinstance(workflow.receiver, list) - else workflow.receiver.dict() - ), - "type": workflow.type, - "name": workflow.name, - "description": workflow.description, - "summary_method": workflow.summary_method, - } - update_item("workflows", workflow.id, updated_data, dbmanager) - else: - query = "INSERT INTO workflows (id, user_id, timestamp, sender, receiver, type, name, description, summary_method) VALUES (?, ?, ?, ?, ?, ?, ?, ?,?)" - args = ( - workflow.id, - workflow.user_id, - workflow.timestamp, - json.dumps(workflow.sender.dict()), - json.dumps( - [receiver.dict() for receiver in workflow.receiver] - if isinstance(workflow.receiver, list) - else workflow.receiver.dict() - ), - workflow.type, - workflow.name, - workflow.description, - workflow.summary_method, - ) - dbmanager.query(query=query, args=args) - - return get_workflows(user_id=workflow.user_id, dbmanager=dbmanager) - - -def delete_workflow(workflow: AgentWorkFlowConfig, dbmanager: DBManager) -> List[Dict[str, Any]]: - """ - Delete a workflow for a specific user from the database. If the workflow does not exist, do nothing. - - :param workflow: The AgentWorkFlowConfig object containing workflow data - :param dbmanager: The DBManager instance to interact with the database - :return: A list of dictionaries, each representing a workflow after deletion - """ - - # delete where workflow.id =id and workflow.user_id = user_id - - query = "DELETE FROM workflows WHERE id = ? AND user_id = ?" - args = (workflow.id, workflow.user_id) - dbmanager.query(query=query, args=args) - - return get_workflows(user_id=workflow.user_id, dbmanager=dbmanager) diff --git a/samples/apps/autogen-studio/autogenstudio/utils/utils.py b/samples/apps/autogen-studio/autogenstudio/utils/utils.py index 8795037cf28..ed533ec3883 100644 --- a/samples/apps/autogen-studio/autogenstudio/utils/utils.py +++ b/samples/apps/autogen-studio/autogenstudio/utils/utils.py @@ -1,14 +1,19 @@ import base64 import hashlib -from typing import List, Dict, Tuple, Union import os +import re import shutil +from datetime import datetime from pathlib import Path -import re -import autogen -from autogen.oai.client import OpenAIWrapper -from ..datamodel import AgentConfig, AgentFlowSpec, AgentWorkFlowConfig, LLMConfig, Model, Skill +from typing import Any, Dict, List, Tuple, Union + from dotenv import load_dotenv +from loguru import logger + +from autogen.coding import DockerCommandLineCodeExecutor, LocalCommandLineCodeExecutor +from autogen.oai.client import ModelClient, OpenAIWrapper + +from ..datamodel import CodeExecutionConfigTypes, Model, Skill from ..version import APP_NAME @@ -22,6 +27,23 @@ def md5_hash(text: str) -> str: return hashlib.md5(text.encode()).hexdigest() +def check_and_cast_datetime_fields(obj: Any) -> Any: + if hasattr(obj, "created_at") and isinstance(obj.created_at, str): + obj.created_at = str_to_datetime(obj.created_at) + + if hasattr(obj, "updated_at") and isinstance(obj.updated_at, str): + obj.updated_at = str_to_datetime(obj.updated_at) + + return obj + + +def str_to_datetime(dt_str: str) -> datetime: + if dt_str[-1] == "Z": + # Replace 'Z' with '+00:00' for UTC timezone + dt_str = dt_str[:-1] + "+00:00" + return datetime.fromisoformat(dt_str) + + def clear_folder(folder_path: str) -> None: """ Clear the contents of a folder. @@ -95,7 +117,16 @@ def get_file_type(file_path: str) -> str: CSV_EXTENSIONS = {".csv", ".xlsx"} # Supported image extensions - IMAGE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".svg", ".webp"} + IMAGE_EXTENSIONS = { + ".png", + ".jpg", + ".jpeg", + ".gif", + ".bmp", + ".tiff", + ".svg", + ".webp", + } # Supported (web) video extensions VIDEO_EXTENSIONS = {".mp4", ".webm", ".ogg", ".mov", ".avi", ".wmv"} @@ -196,20 +227,42 @@ def get_modified_files(start_timestamp: float, end_timestamp: float, source_dir: return modified_files -def init_app_folders(app_file_path: str) -> Dict[str, str]: +def get_app_root() -> str: """ - Initialize folders needed for a web server, such as static file directories - and user-specific data directories. Also load any .env file if it exists. + Get the root directory of the application. - :param root_file_path: The root directory where webserver folders will be created - :return: A dictionary with the path of each created folder + :return: The root directory of the application. """ - app_name = f".{APP_NAME}" default_app_root = os.path.join(os.path.expanduser("~"), app_name) if not os.path.exists(default_app_root): os.makedirs(default_app_root, exist_ok=True) app_root = os.environ.get("AUTOGENSTUDIO_APPDIR") or default_app_root + return app_root + + +def get_db_uri(app_root: str) -> str: + """ + Get the default database URI for the application. + + :param app_root: The root directory of the application. + :return: The default database URI. + """ + db_uri = f"sqlite:///{os.path.join(app_root, 'database.sqlite')}" + db_uri = os.environ.get("AUTOGENSTUDIO_DATABASE_URI") or db_uri + logger.info(f"Using database URI: {db_uri}") + return db_uri + + +def init_app_folders(app_file_path: str) -> Dict[str, str]: + """ + Initialize folders needed for a web server, such as static file directories + and user-specific data directories. Also load any .env file if it exists. + + :param root_file_path: The root directory where webserver folders will be created + :return: A dictionary with the path of each created folder + """ + app_root = get_app_root() if not os.path.exists(app_root): os.makedirs(app_root, exist_ok=True) @@ -217,7 +270,7 @@ def init_app_folders(app_file_path: str) -> Dict[str, str]: # load .env file if it exists env_file = os.path.join(app_root, ".env") if os.path.exists(env_file): - print(f"Loading environment variables from {env_file}") + logger.info(f"Loaded environment variables from {env_file}") load_dotenv(env_file) files_static_root = os.path.join(app_root, "files/") @@ -230,8 +283,9 @@ def init_app_folders(app_file_path: str) -> Dict[str, str]: "files_static_root": files_static_root, "static_folder_root": static_folder_root, "app_root": app_root, + "database_engine_uri": get_db_uri(app_root=app_root), } - print(f"Initialized application data folder: {app_root}") + logger.info(f"Initialized application data folder: {app_root}") return folders @@ -255,11 +309,11 @@ def get_skills_from_prompt(skills: List[Skill], work_dir: str) -> str: for skill in skills: prompt += f""" -##### Begin of {skill.title} ##### +##### Begin of {skill.name} ##### {skill.content} -#### End of {skill.title} #### +#### End of {skill.name} #### """ @@ -287,7 +341,6 @@ def delete_files_in_folder(folders: Union[str, List[str]]) -> None: for folder in folders: # Check if the folder exists if not os.path.isdir(folder): - print(f"The folder {folder} does not exist.") continue # List all the entries in the directory @@ -303,56 +356,7 @@ def delete_files_in_folder(folders: Union[str, List[str]]) -> None: shutil.rmtree(path) except Exception as e: # Print the error message and skip - print(f"Failed to delete {path}. Reason: {e}") - - -def get_default_agent_config(work_dir: str) -> AgentWorkFlowConfig: - """ - Get a default agent flow config . - """ - - llm_config = LLMConfig( - config_list=[{"model": "gpt-4"}], - temperature=0, - ) - - USER_PROXY_INSTRUCTIONS = """If the request has been addressed sufficiently, summarize the answer and end with the word TERMINATE. Otherwise, ask a follow-up question. - """ - - userproxy_spec = AgentFlowSpec( - type="userproxy", - config=AgentConfig( - name="user_proxy", - human_input_mode="NEVER", - system_message=USER_PROXY_INSTRUCTIONS, - code_execution_config={ - "work_dir": work_dir, - "use_docker": False, - }, - max_consecutive_auto_reply=10, - llm_config=llm_config, - is_termination_msg=lambda x: x.get("content", "").rstrip().endswith("TERMINATE"), - ), - ) - - assistant_spec = AgentFlowSpec( - type="assistant", - config=AgentConfig( - name="primary_assistant", - system_message=autogen.AssistantAgent.DEFAULT_SYSTEM_MESSAGE, - llm_config=llm_config, - ), - ) - - flow_config = AgentWorkFlowConfig( - name="default", - sender=userproxy_spec, - receiver=assistant_spec, - type="default", - description="Default agent flow config", - ) - - return flow_config + logger.info(f"Failed to delete {path}. Reason: {e}") def extract_successful_code_blocks(messages: List[Dict[str, str]]) -> List[str]: @@ -389,7 +393,7 @@ def sanitize_model(model: Model): Sanitize model dictionary to remove None values and empty strings and only keep valid keys. """ if isinstance(model, Model): - model = model.dict() + model = model.model_dump() valid_keys = ["model", "base_url", "api_key", "api_type", "api_version"] # only add key if value is not None sanitized_model = {k: v for k, v in model.items() if (v is not None and v != "") and k in valid_keys} @@ -407,16 +411,36 @@ def test_model(model: Model): return response.choices[0].message.content -# summarize_chat_history (messages, model) .. returns a summary of the chat history +def load_code_execution_config(code_execution_type: CodeExecutionConfigTypes, work_dir: str): + """ + Load the code execution configuration based on the code execution type. + + :param code_execution_type: The code execution type. + :param work_dir: The working directory to store code execution files. + :return: The code execution configuration. + + """ + work_dir = Path(work_dir) + work_dir.mkdir(exist_ok=True) + executor = None + if code_execution_type == CodeExecutionConfigTypes.local: + executor = LocalCommandLineCodeExecutor(work_dir=work_dir) + elif code_execution_type == CodeExecutionConfigTypes.docker: + executor = DockerCommandLineCodeExecutor(work_dir=work_dir) + elif code_execution_type == CodeExecutionConfigTypes.none: + return False + else: + raise ValueError(f"Invalid code execution type: {code_execution_type}") + code_execution_config = { + "executor": executor, + } + return code_execution_config -def summarize_chat_history(task: str, messages: List[Dict[str, str]], model: Model): +def summarize_chat_history(task: str, messages: List[Dict[str, str]], client: ModelClient): """ Summarize the chat history using the model endpoint and returning the response. """ - - sanitized_model = sanitize_model(model) - client = OpenAIWrapper(config_list=[sanitized_model]) summarization_system_prompt = f""" You are a helpful assistant that is able to review the chat history between a set of agents (userproxy agents, assistants etc) as they try to address a given TASK and provide a summary. Be SUCCINCT but also comprehensive enough to allow others (who cannot see the chat history) understand and recreate the solution. @@ -424,7 +448,7 @@ def summarize_chat_history(task: str, messages: List[Dict[str, str]], model: Mod === {task} === - The summary should focus on extracting the actual solution to the task from the chat history (assuming the task was addressed) such that any other agent reading the summary will understand what the actual solution is. Use a neutral tone and DO NOT directly mention the agents. Instead only focus on the actions that were carried out (e.g. do not say 'assistant agent generated some code visualization code ..' instead say say 'visualization code was generated ..' ). + The summary should focus on extracting the actual solution to the task from the chat history (assuming the task was addressed) such that any other agent reading the summary will understand what the actual solution is. Use a neutral tone and DO NOT directly mention the agents. Instead only focus on the actions that were carried out (e.g. do not say 'assistant agent generated some code visualization code ..' instead say say 'visualization code was generated ..'. The answer should be framed as a response to the user task. E.g. if the task is "What is the height of the Eiffel tower", the summary should be "The height of the Eiffel Tower is ..."). """ summarization_prompt = [ { diff --git a/samples/apps/autogen-studio/autogenstudio/version.py b/samples/apps/autogen-studio/autogenstudio/version.py index 18b7f42aac3..bafe37f75b1 100644 --- a/samples/apps/autogen-studio/autogenstudio/version.py +++ b/samples/apps/autogen-studio/autogenstudio/version.py @@ -1,3 +1,3 @@ -VERSION = "0.0.54" +VERSION = "0.0.56rc9" __version__ = VERSION APP_NAME = "autogenstudio" diff --git a/samples/apps/autogen-studio/autogenstudio/web/app.py b/samples/apps/autogen-studio/autogenstudio/web/app.py index 48c29ac3803..76ab8139ebc 100644 --- a/samples/apps/autogen-studio/autogenstudio/web/app.py +++ b/samples/apps/autogen-studio/autogenstudio/web/app.py @@ -1,26 +1,23 @@ import asyncio -from contextlib import asynccontextmanager -import json import os import queue import threading import traceback +from contextlib import asynccontextmanager +from typing import Any + from fastapi import FastAPI, WebSocket, WebSocketDisconnect from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles -from fastapi import HTTPException +from loguru import logger from openai import OpenAIError -from ..version import VERSION, APP_NAME -from ..datamodel import ( - DBWebRequestModel, - DeleteMessageWebRequestModel, - Message, - Session, -) -from ..utils import md5_hash, init_app_folders, DBManager, dbutils, test_model from ..chatmanager import AutoGenChatManager, WebSocketConnectionManager - +from ..database import workflow_from_id +from ..database.dbmanager import DBManager +from ..datamodel import Agent, Message, Model, Response, Session, Skill, Workflow +from ..utils import check_and_cast_datetime_fields, init_app_folders, md5_hash, test_model +from ..version import VERSION managers = {"chat": None} # manage calls to autogen # Create thread-safe queue for messages between api thread and autogen threads @@ -28,18 +25,29 @@ active_connections = [] active_connections_lock = asyncio.Lock() websocket_manager = WebSocketConnectionManager( - active_connections=active_connections, active_connections_lock=active_connections_lock + active_connections=active_connections, + active_connections_lock=active_connections_lock, ) def message_handler(): while True: message = message_queue.get() - print("Active Connections: ", [client_id for _, client_id in websocket_manager.active_connections]) - print("Current message connection id: ", message["connection_id"]) + logger.info( + "** Processing Agent Message on Queue: Active Connections: " + + str([client_id for _, client_id in websocket_manager.active_connections]) + + " **" + ) for connection, socket_client_id in websocket_manager.active_connections: if message["connection_id"] == socket_client_id: + logger.info( + f"Sending message to connection_id: {message['connection_id']}. Connection ID: {socket_client_id}" + ) asyncio.run(websocket_manager.send_message(message, connection)) + else: + logger.info( + f"Skipping message for connection_id: {message['connection_id']}. Connection ID: {socket_client_id}" + ) message_queue.task_done() @@ -47,10 +55,19 @@ def message_handler(): message_handler_thread.start() +app_file_path = os.path.dirname(os.path.abspath(__file__)) +folders = init_app_folders(app_file_path) +ui_folder_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ui") + +database_engine_uri = folders["database_engine_uri"] +dbmanager = DBManager(engine_uri=database_engine_uri) + + @asynccontextmanager async def lifespan(app: FastAPI): print("***** App started *****") managers["chat"] = AutoGenChatManager(message_queue=message_queue) + dbmanager.create_db_and_tables() yield # Close all active connections @@ -76,477 +93,312 @@ async def lifespan(app: FastAPI): ) -app_file_path = os.path.dirname(os.path.abspath(__file__)) -# init folders skills, workdir, static, files etc -folders = init_app_folders(app_file_path) -ui_folder_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ui") - api = FastAPI(root_path="/api") # mount an api route such that the main route serves the ui and the /api app.mount("/api", api) app.mount("/", StaticFiles(directory=ui_folder_path, html=True), name="ui") -api.mount("/files", StaticFiles(directory=folders["files_static_root"], html=True), name="files") +api.mount( + "/files", + StaticFiles(directory=folders["files_static_root"], html=True), + name="files", +) -db_path = os.path.join(folders["app_root"], "database.sqlite") -dbmanager = DBManager(path=db_path) # manage database operations # manage websocket connections -@api.post("/messages") -async def add_message(req: DBWebRequestModel): - message = Message(**req.message.dict()) - user_history = dbutils.get_messages(user_id=message.user_id, session_id=req.message.session_id, dbmanager=dbmanager) - - # save incoming message to db - dbutils.create_message(message=message, dbmanager=dbmanager) - user_dir = os.path.join(folders["files_static_root"], "user", md5_hash(message.user_id)) - os.makedirs(user_dir, exist_ok=True) - +def create_entity(model: Any, model_class: Any, filters: dict = None): + """Create a new entity""" + model = check_and_cast_datetime_fields(model) try: - response_message: Message = managers["chat"].chat( - message=message, - history=user_history, - user_dir=user_dir, - flow_config=req.workflow, - connection_id=req.connection_id, - ) + response: Response = dbmanager.upsert(model) + return response.model_dump(mode="json") - # save agent's response to db - messages = dbutils.create_message(message=response_message, dbmanager=dbmanager) - response = { - "status": True, - "message": "Message processed successfully", - "data": messages, - # "metadata": json.loads(response_message.metadata), - } - return response - except Exception as ex_error: - print(traceback.format_exc()) - return { - "status": False, - "message": "Error occurred while processing message: " + str(ex_error), - } - - -@api.get("/messages") -async def get_messages(user_id: str = None, session_id: str = None): - if user_id is None: - raise HTTPException(status_code=400, detail="user_id is required") - try: - user_history = dbutils.get_messages(user_id=user_id, session_id=session_id, dbmanager=dbmanager) - - return { - "status": True, - "data": user_history, - "message": "Messages retrieved successfully", - } except Exception as ex_error: print(ex_error) return { "status": False, - "message": "Error occurred while retrieving messages: " + str(ex_error), + "message": f"Error occurred while creating {model_class.__name__}: " + str(ex_error), } -@api.get("/gallery") -async def get_gallery_items(gallery_id: str = None): - try: - gallery = dbutils.get_gallery(gallery_id=gallery_id, dbmanager=dbmanager) - return { - "status": True, - "data": gallery, - "message": "Gallery items retrieved successfully", - } - except Exception as ex_error: - print(ex_error) - return { - "status": False, - "message": "Error occurred while retrieving messages: " + str(ex_error), - } +def list_entity( + model_class: Any, + filters: dict = None, + return_json: bool = True, + order: str = "desc", +): + """List all entities for a user""" + return dbmanager.get(model_class, filters=filters, return_json=return_json, order=order) -@api.get("/sessions") -async def get_user_sessions(user_id: str = None): - """Return a list of all sessions for a user""" - if user_id is None: - raise HTTPException(status_code=400, detail="user_id is required") +def delete_entity(model_class: Any, filters: dict = None): + """Delete an entity""" - try: - user_sessions = dbutils.get_sessions(user_id=user_id, dbmanager=dbmanager) + return dbmanager.delete(filters=filters, model_class=model_class) - return { - "status": True, - "data": user_sessions, - "message": "Sessions retrieved successfully", - } - except Exception as ex_error: - print(ex_error) - return { - "status": False, - "message": "Error occurred while retrieving sessions: " + str(ex_error), - } - - -@api.post("/sessions") -async def create_user_session(req: DBWebRequestModel): - """Create a new session for a user""" - # print(req.session, "**********" ) - - try: - session = Session(user_id=req.session.user_id, flow_config=req.session.flow_config) - user_sessions = dbutils.create_session(user_id=req.user_id, session=session, dbmanager=dbmanager) - - return { - "status": True, - "message": "Session created successfully", - "data": user_sessions, - } - except Exception as ex_error: - print(traceback.format_exc()) - return { - "status": False, - "message": "Error occurred while creating session: " + str(ex_error), - } - - -@api.post("/sessions/rename") -async def rename_user_session(name: str, req: DBWebRequestModel): - """Rename a session for a user""" - print("Rename: " + name) - print("renaming session for user: " + req.user_id + " to: " + name) - try: - session = dbutils.rename_session(name=name, session=req.session, dbmanager=dbmanager) - return { - "status": True, - "message": "Session renamed successfully", - "data": session, - } - except Exception as ex_error: - print(traceback.format_exc()) - return { - "status": False, - "message": "Error occurred while renaming session: " + str(ex_error), - } - - -@api.post("/sessions/publish") -async def publish_user_session_to_gallery(req: DBWebRequestModel): - """Create a new session for a user""" - - try: - gallery_item = dbutils.create_gallery(req.session, tags=req.tags, dbmanager=dbmanager) - return { - "status": True, - "message": "Session successfully published", - "data": gallery_item, - } - except Exception as ex_error: - print(traceback.format_exc()) - return { - "status": False, - "message": "Error occurred while publishing session: " + str(ex_error), - } +@api.get("/skills") +async def list_skills(user_id: str): + """List all skills for a user""" + filters = {"user_id": user_id} + return list_entity(Skill, filters=filters) -@api.delete("/sessions/delete") -async def delete_user_session(req: DBWebRequestModel): - """Delete a session for a user""" - try: - sessions = dbutils.delete_session(session=req.session, dbmanager=dbmanager) - return { - "status": True, - "message": "Session deleted successfully", - "data": sessions, - } - except Exception as ex_error: - print(traceback.format_exc()) - return { - "status": False, - "message": "Error occurred while deleting session: " + str(ex_error), - } +@api.post("/skills") +async def create_skill(skill: Skill): + """Create a new skill""" + filters = {"user_id": skill.user_id} + return create_entity(skill, Skill, filters=filters) -@api.post("/messages/delete") -async def remove_message(req: DeleteMessageWebRequestModel): - """Delete a message from the database""" +@api.delete("/skills/delete") +async def delete_skill(skill_id: int, user_id: str): + """Delete a skill""" + filters = {"id": skill_id, "user_id": user_id} + return delete_entity(Skill, filters=filters) - try: - messages = dbutils.delete_message( - user_id=req.user_id, msg_id=req.msg_id, session_id=req.session_id, dbmanager=dbmanager - ) - return { - "status": True, - "message": "Message deleted successfully", - "data": messages, - } - except Exception as ex_error: - print(ex_error) - return { - "status": False, - "message": "Error occurred while deleting message: " + str(ex_error), - } +@api.get("/models") +async def list_models(user_id: str): + """List all models for a user""" + filters = {"user_id": user_id} + return list_entity(Model, filters=filters) -@api.get("/skills") -async def get_user_skills(user_id: str): - try: - skills = dbutils.get_skills(user_id, dbmanager=dbmanager) - return { - "status": True, - "message": "Skills retrieved successfully", - "data": skills, - } - except Exception as ex_error: - print(ex_error) - return { - "status": False, - "message": "Error occurred while retrieving skills: " + str(ex_error), - } +@api.post("/models") +async def create_model(model: Model): + """Create a new model""" + return create_entity(model, Model) -@api.post("/skills") -async def create_user_skills(req: DBWebRequestModel): +@api.post("/models/test") +async def test_model_endpoint(model: Model): + """Test a model""" try: - skills = dbutils.upsert_skill(skill=req.skill, dbmanager=dbmanager) + response = test_model(model) return { "status": True, - "message": "Skills retrieved successfully", - "data": skills, + "message": "Model tested successfully", + "data": response, } - - except Exception as ex_error: - print(ex_error) + except (OpenAIError, Exception) as ex_error: return { "status": False, - "message": "Error occurred while creating skills: " + str(ex_error), + "message": "Error occurred while testing model: " + str(ex_error), } -@api.delete("/skills/delete") -async def delete_user_skills(req: DBWebRequestModel): - """Delete a skill for a user""" +@api.delete("/models/delete") +async def delete_model(model_id: int, user_id: str): + """Delete a model""" + filters = {"id": model_id, "user_id": user_id} + return delete_entity(Model, filters=filters) - try: - skills = dbutils.delete_skill(req.skill, dbmanager=dbmanager) - return { - "status": True, - "message": "Skill deleted successfully", - "data": skills, - } +@api.get("/agents") +async def list_agents(user_id: str): + """List all agents for a user""" + filters = {"user_id": user_id} + return list_entity(Agent, filters=filters) - except Exception as ex_error: - print(ex_error) - return { - "status": False, - "message": "Error occurred while deleting skill: " + str(ex_error), - } +@api.post("/agents") +async def create_agent(agent: Agent): + """Create a new agent""" + return create_entity(agent, Agent) -@api.get("/agents") -async def get_user_agents(user_id: str): - try: - agents = dbutils.get_agents(user_id, dbmanager=dbmanager) - return { - "status": True, - "message": "Agents retrieved successfully", - "data": agents, - } - except Exception as ex_error: - print(ex_error) - return { - "status": False, - "message": "Error occurred while retrieving agents: " + str(ex_error), - } +@api.delete("/agents/delete") +async def delete_agent(agent_id: int, user_id: str): + """Delete an agent""" + filters = {"id": agent_id, "user_id": user_id} + return delete_entity(Agent, filters=filters) -@api.post("/agents") -async def create_user_agents(req: DBWebRequestModel): - """Create a new agent for a user""" +@api.post("/agents/link/model/{agent_id}/{model_id}") +async def link_agent_model(agent_id: int, model_id: int): + """Link a model to an agent""" + return dbmanager.link(link_type="agent_model", primary_id=agent_id, secondary_id=model_id) - try: - agents = dbutils.upsert_agent(agent_flow_spec=req.agent, dbmanager=dbmanager) - return { - "status": True, - "message": "Agent created successfully", - "data": agents, - } +@api.delete("/agents/link/model/{agent_id}/{model_id}") +async def unlink_agent_model(agent_id: int, model_id: int): + """Unlink a model from an agent""" + return dbmanager.unlink(link_type="agent_model", primary_id=agent_id, secondary_id=model_id) - except Exception as ex_error: - print(traceback.format_exc()) - return { - "status": False, - "message": "Error occurred while creating agent: " + str(ex_error), - } +@api.get("/agents/link/model/{agent_id}") +async def get_agent_models(agent_id: int): + """Get all models linked to an agent""" + return dbmanager.get_linked_entities("agent_model", agent_id, return_json=True) -@api.delete("/agents/delete") -async def delete_user_agent(req: DBWebRequestModel): - """Delete an agent for a user""" - try: - agents = dbutils.delete_agent(agent=req.agent, dbmanager=dbmanager) +@api.post("/agents/link/skill/{agent_id}/{skill_id}") +async def link_agent_skill(agent_id: int, skill_id: int): + """Link an a skill to an agent""" + return dbmanager.link(link_type="agent_skill", primary_id=agent_id, secondary_id=skill_id) - return { - "status": True, - "message": "Agent deleted successfully", - "data": agents, - } - except Exception as ex_error: - print(traceback.format_exc()) - return { - "status": False, - "message": "Error occurred while deleting agent: " + str(ex_error), - } +@api.delete("/agents/link/skill/{agent_id}/{skill_id}") +async def unlink_agent_skill(agent_id: int, skill_id: int): + """Unlink an a skill from an agent""" + return dbmanager.unlink(link_type="agent_skill", primary_id=agent_id, secondary_id=skill_id) -@api.get("/models") -async def get_user_models(user_id: str): - try: - models = dbutils.get_models(user_id, dbmanager=dbmanager) +@api.get("/agents/link/skill/{agent_id}") +async def get_agent_skills(agent_id: int): + """Get all skills linked to an agent""" + return dbmanager.get_linked_entities("agent_skill", agent_id, return_json=True) - return { - "status": True, - "message": "Models retrieved successfully", - "data": models, - } - except Exception as ex_error: - print(ex_error) - return { - "status": False, - "message": "Error occurred while retrieving models: " + str(ex_error), - } +@api.post("/agents/link/agent/{primary_agent_id}/{secondary_agent_id}") +async def link_agent_agent(primary_agent_id: int, secondary_agent_id: int): + """Link an agent to another agent""" + return dbmanager.link( + link_type="agent_agent", + primary_id=primary_agent_id, + secondary_id=secondary_agent_id, + ) -@api.post("/models") -async def create_user_models(req: DBWebRequestModel): - """Create a new model for a user""" - try: - models = dbutils.upsert_model(model=req.model, dbmanager=dbmanager) +@api.delete("/agents/link/agent/{primary_agent_id}/{secondary_agent_id}") +async def unlink_agent_agent(primary_agent_id: int, secondary_agent_id: int): + """Unlink an agent from another agent""" + return dbmanager.unlink( + link_type="agent_agent", + primary_id=primary_agent_id, + secondary_id=secondary_agent_id, + ) - return { - "status": True, - "message": "Model created successfully", - "data": models, - } - except Exception as ex_error: - print(traceback.format_exc()) - return { - "status": False, - "message": "Error occurred while creating model: " + str(ex_error), - } +@api.get("/agents/link/agent/{agent_id}") +async def get_linked_agents(agent_id: int): + """Get all agents linked to an agent""" + return dbmanager.get_linked_entities("agent_agent", agent_id, return_json=True) -@api.post("/models/test") -async def test_user_models(req: DBWebRequestModel): - """Test a model to verify it works""" +@api.get("/workflows") +async def list_workflows(user_id: str): + """List all workflows for a user""" + filters = {"user_id": user_id} + return list_entity(Workflow, filters=filters) - try: - response = test_model(model=req.model) - return { - "status": True, - "message": "Model tested successfully", - "data": response, - } - except OpenAIError as oai_error: - print(traceback.format_exc()) - return { - "status": False, - "message": "Error occurred while testing model: " + str(oai_error), - } - except Exception as ex_error: - print(traceback.format_exc()) - return { - "status": False, - "message": "Error occurred while testing model: " + str(ex_error), - } +@api.get("/workflows/{workflow_id}") +async def get_workflow(workflow_id: int, user_id: str): + """Get a workflow""" + filters = {"id": workflow_id, "user_id": user_id} + return list_entity(Workflow, filters=filters) -@api.delete("/models/delete") -async def delete_user_model(req: DBWebRequestModel): - """Delete a model for a user""" +@api.post("/workflows") +async def create_workflow(workflow: Workflow): + """Create a new workflow""" + return create_entity(workflow, Workflow) - try: - models = dbutils.delete_model(model=req.model, dbmanager=dbmanager) - return { - "status": True, - "message": "Model deleted successfully", - "data": models, - } +@api.delete("/workflows/delete") +async def delete_workflow(workflow_id: int, user_id: str): + """Delete a workflow""" + filters = {"id": workflow_id, "user_id": user_id} + return delete_entity(Workflow, filters=filters) + + +@api.post("/workflows/link/agent/{workflow_id}/{agent_id}/{agent_type}") +async def link_workflow_agent(workflow_id: int, agent_id: int, agent_type: str): + """Link an agent to a workflow""" + return dbmanager.link( + link_type="workflow_agent", + primary_id=workflow_id, + secondary_id=agent_id, + agent_type=agent_type, + ) + + +@api.delete("/workflows/link/agent/{workflow_id}/{agent_id}/{agent_type}") +async def unlink_workflow_agent(workflow_id: int, agent_id: int, agent_type: str): + """Unlink an agent from a workflow""" + return dbmanager.unlink( + link_type="workflow_agent", + primary_id=workflow_id, + secondary_id=agent_id, + agent_type=agent_type, + ) + + +@api.get("/workflows/link/agent/{workflow_id}/{agent_type}") +async def get_linked_workflow_agents(workflow_id: int, agent_type: str): + """Get all agents linked to a workflow""" + return dbmanager.get_linked_entities( + link_type="workflow_agent", + primary_id=workflow_id, + agent_type=agent_type, + return_json=True, + ) - except Exception as ex_error: - print(traceback.format_exc()) - return { - "status": False, - "message": "Error occurred while deleting model: " + str(ex_error), - } +@api.get("/sessions") +async def list_sessions(user_id: str): + """List all sessions for a user""" + filters = {"user_id": user_id} + return list_entity(Session, filters=filters) -@api.get("/workflows") -async def get_user_workflows(user_id: str): - try: - workflows = dbutils.get_workflows(user_id, dbmanager=dbmanager) - return { - "status": True, - "message": "Workflows retrieved successfully", - "data": workflows, - } - except Exception as ex_error: - print(ex_error) - return { - "status": False, - "message": "Error occurred while retrieving workflows: " + str(ex_error), - } +@api.post("/sessions") +async def create_session(session: Session): + """Create a new session""" + return create_entity(session, Session) -@api.post("/workflows") -async def create_user_workflow(req: DBWebRequestModel): - """Create a new workflow for a user""" - try: - workflow = dbutils.upsert_workflow(workflow=req.workflow, dbmanager=dbmanager) - return { - "status": True, - "message": "Workflow created successfully", - "data": workflow, - } +@api.delete("/sessions/delete") +async def delete_session(session_id: int, user_id: str): + """Delete a session""" + filters = {"id": session_id, "user_id": user_id} + return delete_entity(Session, filters=filters) - except Exception as ex_error: - print(ex_error) - return { - "status": False, - "message": "Error occurred while creating workflow: " + str(ex_error), - } +@api.get("/sessions/{session_id}/messages") +async def list_messages(user_id: str, session_id: int): + """List all messages for a use session""" + filters = {"user_id": user_id, "session_id": session_id} + return list_entity(Message, filters=filters, order="asc", return_json=True) -@api.delete("/workflows/delete") -async def delete_user_workflow(req: DBWebRequestModel): - """Delete a workflow for a user""" +@api.post("/sessions/{session_id}/workflow/{workflow_id}/run") +async def run_session_workflow(message: Message, session_id: int, workflow_id: int): + """Runs a workflow on provided message""" try: - workflow = dbutils.delete_workflow(workflow=req.workflow, dbmanager=dbmanager) - return { - "status": True, - "message": "Workflow deleted successfully", - "data": workflow, - } + user_message_history = ( + dbmanager.get( + Message, + filters={"user_id": message.user_id, "session_id": message.session_id}, + return_json=True, + ).data + if session_id is not None + else [] + ) + # save incoming message + dbmanager.upsert(message) + user_dir = os.path.join(folders["files_static_root"], "user", md5_hash(message.user_id)) + os.makedirs(user_dir, exist_ok=True) + workflow = workflow_from_id(workflow_id, dbmanager=dbmanager) + agent_response: Message = managers["chat"].chat( + message=message, + history=user_message_history, + user_dir=user_dir, + workflow=workflow, + connection_id=message.connection_id, + ) + response: Response = dbmanager.upsert(agent_response) + return response.model_dump(mode="json") except Exception as ex_error: - print(ex_error) + print(traceback.format_exc()) return { "status": False, - "message": "Error occurred while deleting workflow: " + str(ex_error), + "message": "Error occurred while processing message: " + str(ex_error), } @@ -559,11 +411,16 @@ async def get_version(): } +# websockets + + async def process_socket_message(data: dict, websocket: WebSocket, client_id: str): print(f"Client says: {data['type']}") if data["type"] == "user_message": - user_request_body = DBWebRequestModel(**data["data"]) - response = await add_message(user_request_body) + user_message = Message(**data["data"]) + session_id = data["data"].get("session_id", None) + workflow_id = data["data"].get("workflow_id", None) + response = await run_session_workflow(message=user_message, session_id=session_id, workflow_id=workflow_id) response_socket_message = { "type": "agent_response", "data": response, diff --git a/samples/apps/autogen-studio/autogenstudio/workflowmanager.py b/samples/apps/autogen-studio/autogenstudio/workflowmanager.py index 628c7f9f711..8b41caab428 100644 --- a/samples/apps/autogen-studio/autogenstudio/workflowmanager.py +++ b/samples/apps/autogen-studio/autogenstudio/workflowmanager.py @@ -1,22 +1,26 @@ import os -from typing import List, Optional, Union, Dict - -from requests import Session +from datetime import datetime +from typing import Any, Dict, List, Optional, Union import autogen -from .datamodel import AgentConfig, AgentFlowSpec, AgentWorkFlowConfig, Message, SocketMessage -from .utils import get_skills_from_prompt, clear_folder, sanitize_model -from datetime import datetime +from .datamodel import ( + Agent, + AgentType, + Message, + SocketMessage, +) +from .utils import clear_folder, get_skills_from_prompt, load_code_execution_config, sanitize_model -class AutoGenWorkFlowManager: + +class WorkflowManager: """ AutoGenWorkFlowManager class to load agents from a provided configuration and run a chat between them """ def __init__( self, - config: AgentWorkFlowConfig, + workflow: Dict, history: Optional[List[Message]] = None, work_dir: str = None, clear_work_dir: bool = True, @@ -32,20 +36,57 @@ def __init__( history: An optional list of previous messages to populate the agents' history. """ + # TODO - improved typing for workflow self.send_message_function = send_message_function self.connection_id = connection_id self.work_dir = work_dir or "work_dir" if clear_work_dir: clear_folder(self.work_dir) - self.config = config - # given the config, return an AutoGen agent object - self.sender = self.load(config.sender) - # given the config, return an AutoGen agent object - self.receiver = self.load(config.receiver) + self.workflow = workflow + self.sender = self.load(workflow.get("sender")) + self.receiver = self.load(workflow.get("receiver")) self.agent_history = [] if history: - self.populate_history(history) + self._populate_history(history) + + def _serialize_agent( + self, + agent: Agent, + mode: str = "python", + include: Optional[List[str]] = {"config"}, + exclude: Optional[List[str]] = None, + ) -> Dict: + """ """ + # exclude = ["id","created_at", "updated_at","user_id","type"] + exclude = exclude or {} + include = include or {} + if agent.type != AgentType.groupchat: + exclude.update( + { + "config": { + "admin_name", + "messages", + "max_round", + "admin_name", + "speaker_selection_method", + "allow_repeat_speaker", + } + } + ) + else: + include = { + "config": { + "admin_name", + "messages", + "max_round", + "admin_name", + "speaker_selection_method", + "allow_repeat_speaker", + } + } + result = agent.model_dump(warnings=False, exclude=exclude, include=include, mode=mode) + return result["config"] def process_message( self, @@ -83,25 +124,14 @@ def process_message( if request_reply is not False or sender_type == "groupchat": self.agent_history.append(message_payload) # add to history if self.send_message_function: # send over the message queue - socket_msg = SocketMessage(type="agent_message", data=message_payload, connection_id=self.connection_id) + socket_msg = SocketMessage( + type="agent_message", + data=message_payload, + connection_id=self.connection_id, + ) self.send_message_function(socket_msg.dict()) - def _sanitize_history_message(self, message: str) -> str: - """ - Sanitizes the message e.g. remove references to execution completed - - Args: - message: The message to be sanitized. - - Returns: - The sanitized message. - """ - to_replace = ["execution succeeded", "exitcode"] - for replace in to_replace: - message = message.replace(replace, "") - return message - - def populate_history(self, history: List[Message]) -> None: + def _populate_history(self, history: List[Message]) -> None: """ Populates the agent message history from the provided list of messages. @@ -126,19 +156,12 @@ def populate_history(self, history: List[Message]) -> None: silent=True, ) - def sanitize_agent_spec(self, agent_spec: AgentFlowSpec) -> AgentFlowSpec: - """ - Sanitizes the agent spec by setting loading defaults + def sanitize_agent(self, agent: Dict) -> Agent: + """ """ - Args: - config: The agent configuration to be sanitized. - agent_type: The type of the agent. - - Returns: - The sanitized agent configuration. - """ - - agent_spec.config.is_termination_msg = agent_spec.config.is_termination_msg or ( + skills = agent.get("skills", []) + agent = Agent.model_validate(agent) + agent.config.is_termination_msg = agent.config.is_termination_msg or ( lambda x: "TERMINATE" in x.get("content", "").rstrip()[-20:] ) @@ -148,40 +171,33 @@ def get_default_system_message(agent_type: str) -> str: else: return "You are a helpful AI Assistant." - # sanitize llm_config if present - if agent_spec.config.llm_config is not False: + if agent.config.llm_config is not False: config_list = [] - for llm in agent_spec.config.llm_config.config_list: + for llm in agent.config.llm_config.config_list: # check if api_key is present either in llm or env variable if "api_key" not in llm and "OPENAI_API_KEY" not in os.environ: - error_message = f"api_key is not present in llm_config or OPENAI_API_KEY env variable for agent ** {agent_spec.config.name}**. Update your workflow to provide an api_key to use the LLM." + error_message = f"api_key is not present in llm_config or OPENAI_API_KEY env variable for agent ** {agent.config.name}**. Update your workflow to provide an api_key to use the LLM." raise ValueError(error_message) # only add key if value is not None sanitized_llm = sanitize_model(llm) config_list.append(sanitized_llm) - agent_spec.config.llm_config.config_list = config_list - if agent_spec.config.code_execution_config is not False: - code_execution_config = agent_spec.config.code_execution_config or {} - code_execution_config["work_dir"] = self.work_dir - # tbd check if docker is installed - code_execution_config["use_docker"] = False - agent_spec.config.code_execution_config = code_execution_config - - if agent_spec.skills: - # get skill prompt, also write skills to a file named skills.py - skills_prompt = "" - skills_prompt = get_skills_from_prompt(agent_spec.skills, self.work_dir) - if agent_spec.config.system_message: - agent_spec.config.system_message = agent_spec.config.system_message + "\n\n" + skills_prompt - else: - agent_spec.config.system_message = ( - get_default_system_message(agent_spec.type) + "\n\n" + skills_prompt - ) - - return agent_spec - - def load(self, agent_spec: AgentFlowSpec) -> autogen.Agent: + agent.config.llm_config.config_list = config_list + + agent.config.code_execution_config = load_code_execution_config( + agent.config.code_execution_config, work_dir=self.work_dir + ) + + if skills: + skills_prompt = "" + skills_prompt = get_skills_from_prompt(skills, self.work_dir) + if agent.config.system_message: + agent.config.system_message = agent.config.system_message + "\n\n" + skills_prompt + else: + agent.config.system_message = get_default_system_message(agent.type) + "\n\n" + skills_prompt + return agent + + def load(self, agent: Any) -> autogen.Agent: """ Loads an agent based on the provided agent specification. @@ -191,43 +207,40 @@ def load(self, agent_spec: AgentFlowSpec) -> autogen.Agent: Returns: An instance of the loaded agent. """ - agent_spec = self.sanitize_agent_spec(agent_spec) - if agent_spec.type == "groupchat": - agents = [ - self.load(self.sanitize_agent_spec(agent_config)) for agent_config in agent_spec.groupchat_config.agents - ] - group_chat_config = agent_spec.groupchat_config.dict() - group_chat_config["agents"] = agents + if not agent: + raise ValueError( + "An agent configuration in this workflow is empty. Please provide a valid agent configuration." + ) + + linked_agents = agent.get("agents", []) + agent = self.sanitize_agent(agent) + if agent.type == "groupchat": + groupchat_agents = [self.load(agent) for agent in linked_agents] + group_chat_config = self._serialize_agent(agent) + group_chat_config["agents"] = groupchat_agents groupchat = autogen.GroupChat(**group_chat_config) agent = ExtendedGroupChatManager( - groupchat=groupchat, **agent_spec.config.dict(), message_processor=self.process_message + groupchat=groupchat, + message_processor=self.process_message, + llm_config=agent.config.llm_config.model_dump(), ) return agent else: - agent = self.load_agent_config(agent_spec.config, agent_spec.type) + if agent.type == "assistant": + agent = ExtendedConversableAgent( + **self._serialize_agent(agent), + message_processor=self.process_message, + ) + elif agent.type == "userproxy": + agent = ExtendedConversableAgent( + **self._serialize_agent(agent), + message_processor=self.process_message, + ) + else: + raise ValueError(f"Unknown agent type: {agent.type}") return agent - def load_agent_config(self, agent_config: AgentConfig, agent_type: str) -> autogen.Agent: - """ - Loads an agent based on the provided agent configuration. - - Args: - agent_config: The configuration of the agent to be loaded. - agent_type: The type of the agent to be loaded. - - Returns: - An instance of the loaded agent. - """ - if agent_type == "assistant": - agent = ExtendedConversableAgent(**agent_config.dict(), message_processor=self.process_message) - elif agent_type == "userproxy": - agent = ExtendedConversableAgent(**agent_config.dict(), message_processor=self.process_message) - else: - raise ValueError(f"Unknown agent type: {agent_type}") - - return agent - def run(self, message: str, clear_history: bool = False) -> None: """ Initiates a chat between the sender and receiver agents with an initial message @@ -261,6 +274,9 @@ def receive( super().receive(message, sender, request_reply, silent) +"" + + class ExtendedGroupChatManager(autogen.GroupChatManager): def __init__(self, message_processor=None, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/samples/apps/autogen-studio/docs/ara_stockprices.png b/samples/apps/autogen-studio/docs/ara_stockprices.png index fafb830ef1a..f5adf6256e5 100644 Binary files a/samples/apps/autogen-studio/docs/ara_stockprices.png and b/samples/apps/autogen-studio/docs/ara_stockprices.png differ diff --git a/samples/apps/autogen-studio/frontend/src/components/atoms.tsx b/samples/apps/autogen-studio/frontend/src/components/atoms.tsx index 8bc70f89a90..c4c1368a123 100644 --- a/samples/apps/autogen-studio/frontend/src/components/atoms.tsx +++ b/samples/apps/autogen-studio/frontend/src/components/atoms.tsx @@ -4,53 +4,18 @@ import { Cog8ToothIcon, XMarkIcon, ClipboardIcon, - PlusIcon, - UserGroupIcon, - UsersIcon, - ExclamationTriangleIcon, InformationCircleIcon, } from "@heroicons/react/24/outline"; import React, { ReactNode, useEffect, useRef, useState } from "react"; import Icon from "./icons"; -import { - Button, - Divider, - Dropdown, - Input, - MenuProps, - Modal, - Select, - Slider, - Table, - Space, - Tooltip, - message, - theme, -} from "antd"; +import { Modal, Table, Tooltip, theme } from "antd"; import Editor from "@monaco-editor/react"; import Papa from "papaparse"; import remarkGfm from "remark-gfm"; import ReactMarkdown from "react-markdown"; import { atomDark } from "react-syntax-highlighter/dist/esm/styles/prism"; import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; -import { - checkAndSanitizeInput, - fetchJSON, - getServerUrl, - obscureString, - truncateText, -} from "./utils"; -import { - IAgentFlowSpec, - IFlowConfig, - IGroupChatFlowSpec, - ILLMConfig, - IModelConfig, - ISkill, - IStatus, -} from "./types"; -import TextArea from "antd/es/input/TextArea"; -import { appContext } from "../hooks/provider"; +import { truncateText } from "./utils"; const { useToken } = theme; interface CodeProps { @@ -162,12 +127,13 @@ export const Card = ({ border = hoverable ? border : "border-secondary"; return ( -
-
+
{title && (
{title} @@ -176,7 +142,7 @@ export const Card = ({
{subtitle}
{children}
-
+ ); }; @@ -303,7 +269,7 @@ export const MessageBox = ({ title, children, className }: IProps) => { export const GroupView = ({ children, title, - className = " bg-primary ", + className = "text-primary bg-primary ", }: any) => { return (
@@ -590,19 +556,21 @@ export const ControlRowView = ({ value, control, className, + truncateLength = 20, }: { title: string; description: string; - value: string | number; + value: string | number | boolean; control: any; className?: string; + truncateLength?: number; }) => { return (
{title} - {truncateText(value + "", 20)} + {truncateText(value + "", truncateLength)} {" "} @@ -614,291 +582,6 @@ export const ControlRowView = ({ ); }; -export const ModelSelector = ({ - configs, - setConfigs, - className, -}: { - configs: IModelConfig[]; - setConfigs: (configs: IModelConfig[]) => void; - className?: string; -}) => { - // const [configs, setConfigs] = useState(modelConfigs); - const [isModalVisible, setIsModalVisible] = useState(false); - const [newModelConfig, setNewModelConfig] = useState( - null - ); - const [editIndex, setEditIndex] = useState(null); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); - - const [models, setModels] = useState([]); - const serverUrl = getServerUrl(); - - const { user } = React.useContext(appContext); - const listModelsUrl = `${serverUrl}/models?user_id=${user?.email}`; - - // const sanitizeModelConfig = (config: IModelConfig) => { - // const sanitizedConfig: IModelConfig = { model: config.model }; - // if (config.api_key) sanitizedConfig.api_key = config.api_key; - // if (config.base_url) sanitizedConfig.base_url = config.base_url; - // if (config.api_type) sanitizedConfig.api_type = config.api_type; - // if (config.api_version) sanitizedConfig.api_version = config.api_version; - // return sanitizedConfig; - // }; - - const handleRemoveConfig = (index: number) => { - const updatedConfigs = configs.filter((_, i) => i !== index); - - setConfigs(updatedConfigs); - }; - - const showModal = (config: IModelConfig | null, index: number | null) => { - setNewModelConfig(config); - setEditIndex(index); - setIsModalVisible(true); - }; - - const fetchModels = () => { - setError(null); - setLoading(true); - // const fetch; - const payLoad = { - method: "GET", - headers: { - "Content-Type": "application/json", - }, - }; - - const onSuccess = (data: any) => { - if (data && data.status) { - // message.success(data.message); - setModels(data.data); - } else { - message.error(data.message); - } - setLoading(false); - }; - const onError = (err: any) => { - setError(err); - message.error(err.message); - setLoading(false); - }; - fetchJSON(listModelsUrl, payLoad, onSuccess, onError); - }; - - useEffect(() => { - fetchModels(); - }, []); - - const modelItems: MenuProps["items"] = - models.length > 0 - ? models.map((model: IModelConfig, index: number) => ({ - key: index, - label: ( - <> -
{model.model}
-
- {truncateText(model.description || "", 20)} -
- - ), - value: index, - })) - : [ - { - key: -1, - label: <>No models found, - value: 0, - }, - ]; - - const modelOnClick: MenuProps["onClick"] = ({ key }) => { - const selectedIndex = parseInt(key.toString()); - let selectedModel = models[selectedIndex]; - const updatedConfigs = [...configs, selectedModel]; - setConfigs(updatedConfigs); - }; - - const menuStyle: React.CSSProperties = { - boxShadow: "none", - }; - - const { token } = useToken(); - const contentStyle: React.CSSProperties = { - backgroundColor: token.colorBgElevated, - borderRadius: token.borderRadiusLG, - boxShadow: token.boxShadowSecondary, - }; - - const addModelsMessage = ( - - {" "} - Please - create models in the Model tab - - ); - - const AddModelsDropDown = () => { - return ( - ( -
- {React.cloneElement(menu as React.ReactElement, { - style: menuStyle, - })} - {models.length === 0 && ( - <> - - -
{addModelsMessage}
- - )} -
- )} - > -
- add -
-
- ); - }; - - const handleOk = () => { - if (newModelConfig?.model.trim()) { - const sanitizedConfig = newModelConfig; - - if (editIndex !== null) { - // Edit existing model - const updatedConfigs = [...configs]; - updatedConfigs[editIndex] = sanitizedConfig; - setConfigs(updatedConfigs); - } else { - // Add new model - setConfigs([...configs, sanitizedConfig]); - } - setIsModalVisible(false); - setNewModelConfig(null); - setEditIndex(null); - } else { - // Handle case where 'model' field is empty - // Could provide user feedback here (e.g., input validation error) - message.error("Model name cannot be empty"); - } - }; - - const handleCancel = () => { - setIsModalVisible(false); - setNewModelConfig(null); - setEditIndex(null); - }; - - const updateNewModelConfig = (field: keyof IModelConfig, value: string) => { - setNewModelConfig((prevState) => - prevState ? { ...prevState, [field]: value } : null - ); - }; - - const modelButtons = configs.map((config, i) => { - const tooltipText = ( - <> -
{config.model}
- {config.base_url &&
{config.base_url}
} - {config.api_key &&
{obscureString(config.api_key, 3)}
} -
- {truncateText(config.description || "", 90)} -
- - ); - return ( -
showModal(config, i)} - > -
- {" "} - -
{config.model}
{" "} -
-
{ - e.stopPropagation(); // Prevent opening the modal to edit - handleRemoveConfig(i); - }} - className="ml-1 text-primary hover:text-accent duration-300" - > - -
-
-
- ); - }); - - return ( -
-
- {modelButtons} - -
- - Cancel - , - , - ]} - > -
Enter parameters for your model.
- updateNewModelConfig("model", e.target.value)} - /> - updateNewModelConfig("api_key", e.target.value)} - /> - updateNewModelConfig("base_url", e.target.value)} - /> - updateNewModelConfig("api_type", e.target.value)} - /> - - updateNewModelConfig("api_version", e.target.value)} - /> -
-
- ); -}; - export const BounceLoader = ({ className, title = "", @@ -937,7 +620,7 @@ export const ImageLoader = ({ Dynamic content setIsLoading(false)} @@ -1077,946 +760,6 @@ export const PdfViewer = ({ url }: { url: string }) => { ); }; -export const AgentFlowSpecView = ({ - title = "Agent Specification", - flowSpec, - setFlowSpec, -}: { - title: string; - flowSpec: IAgentFlowSpec; - setFlowSpec: (newFlowSpec: IAgentFlowSpec) => void; - editMode?: boolean; -}) => { - // Local state for the FlowView component - const [localFlowSpec, setLocalFlowSpec] = - React.useState(flowSpec); - - // Required to monitor localAgent updates that occur in GroupChatFlowSpecView and reflect updates. - useEffect(() => { - setLocalFlowSpec(flowSpec); - }, [flowSpec]); - - // Event handlers for updating local state and propagating changes - - const onControlChange = (value: any, key: string) => { - if (key === "llm_config") { - if (value.config_list.length === 0) { - value = false; - } - } - const updatedFlowSpec = { - ...localFlowSpec, - config: { ...localFlowSpec.config, [key]: value }, - }; - - setLocalFlowSpec(updatedFlowSpec); - setFlowSpec(updatedFlowSpec); - }; - - const llm_config: ILLMConfig = localFlowSpec?.config?.llm_config || { - config_list: [], - temperature: 0.1, - }; - - const nameValidation = checkAndSanitizeInput(flowSpec?.config?.name); - - return ( - <> -
{title}
- {flowSpec?.config?.name}
- className="mb-4 bg-primary " - > - - { - onControlChange(e.target.value, "name"); - }} - /> - {!nameValidation.status && ( -
- {nameValidation.message} -
- )} - - } - /> - - { - onControlChange(e.target.value, "description"); - }} - /> - } - /> - - { - onControlChange(value, "max_consecutive_auto_reply"); - }} - /> - } - /> - - { - onControlChange(e.target.value, "default_auto_reply"); - }} - /> - } - /> - - { - onControlChange(value, "human_input_mode"); - }} - options={ - [ - { label: "NEVER", value: "NEVER" }, - // { label: "TERMINATE", value: "TERMINATE" }, - // { label: "ALWAYS", value: "ALWAYS" }, - ] as any - } - /> - } - /> - - {llm_config && llm_config.config_list.length > 0 && ( - { - onControlChange(e.target.value, "system_message"); - }} - /> - } - /> - )} - - {llm_config && ( - { - const llm_config = { - ...(flowSpec.config.llm_config || { temperature: 0.1 }), - config_list, - }; - onControlChange(llm_config, "llm_config"); - }} - /> - } - /> - )} - - {llm_config && llm_config.config_list.length > 0 && ( - { - const llm_config = { - ...flowSpec.config.llm_config, - temperature: value, - }; - onControlChange(llm_config, "llm_config"); - }} - /> - } - /> - )} - - { - { - const updatedFlowSpec = { - ...localFlowSpec, - skills, - }; - setLocalFlowSpec(updatedFlowSpec); - setFlowSpec(updatedFlowSpec); - }} - /> - } - /> - } - - - ); -}; - -interface SkillSelectorProps { - skills: ISkill[]; - setSkills: (skills: ISkill[]) => void; - className?: string; -} - -export const SkillSelector: React.FC = ({ - skills, - setSkills, - className, -}) => { - const [isModalVisible, setIsModalVisible] = useState(false); - const [showSkillModal, setShowSkillModal] = React.useState(false); - const [newSkill, setNewSkill] = useState(null); - - const [localSkills, setLocalSkills] = useState(skills); - const [selectedSkill, setSelectedSkill] = useState(null); - - const handleRemoveSkill = (index: number) => { - const updatedSkills = localSkills.filter((_, i) => i !== index); - setLocalSkills(updatedSkills); - setSkills(updatedSkills); - }; - - const handleAddSkill = () => { - if (newSkill) { - const updatedSkills = [...localSkills, newSkill]; - setLocalSkills(updatedSkills); - setSkills(updatedSkills); - setNewSkill(null); - } - }; - - useEffect(() => { - if (selectedSkill) { - setShowSkillModal(true); - } - }, [selectedSkill]); - - return ( - <> - { - setShowSkillModal(false); - setSelectedSkill(null); - }} - onCancel={() => { - setShowSkillModal(false); - setSelectedSkill(null); - }} - > - {selectedSkill && ( -
-
{selectedSkill.file_name}
- -
- )} -
- -
- {localSkills.map((skill, index) => ( -
- { - setSelectedSkill(skill); - }} - className=" inline-block " - > - {skill.title} - - handleRemoveSkill(index)} - className="ml-1 text-primary hover:text-accent duration-300 w-4 h-4 inline-block" - /> -
- ))} - -
{ - setIsModalVisible(true); - }} - > - add -
-
- - setIsModalVisible(false)} - footer={[ - , - , - ]} - > - - - - ); -}; - -export const SkillLoader = ({ - skill, - setSkill, -}: { - skill: ISkill | null; - setSkill: (skill: ISkill | null) => void; -}) => { - const [skills, setSkills] = useState([]); - const [loading, setLoading] = useState(false); - const [error, setError] = React.useState({ - status: true, - message: "All good", - }); - const serverUrl = getServerUrl(); - const { user } = React.useContext(appContext); - const listSkillsUrl = `${serverUrl}/skills?user_id=${user?.email}`; - - const fetchSkills = () => { - setError(null); - setLoading(true); - // const fetch; - const payLoad = { - method: "GET", - headers: { - "Content-Type": "application/json", - }, - }; - - const onSuccess = (data: any) => { - if (data && data.status) { - message.success(data.message); - setSkills(data.data); - if (data.data.length > 0) { - setSkill(data.data[0]); - } - } else { - message.error(data.message); - } - setLoading(false); - }; - const onError = (err: any) => { - setError(err); - message.error(err.message); - setLoading(false); - }; - fetchJSON(listSkillsUrl, payLoad, onSuccess, onError); - }; - - useEffect(() => { - fetchSkills(); - }, []); - - const skillOptions = skills.map((skill: ISkill, index: number) => ({ - label: skill.title, - value: index, - })); - return ( -
- - - {skills && ( - <> - ({ - label: spec.config.name, - value: index, - }))} - /> -
- )} - {/* {JSON.stringify(localAgent)} */} - - ); -}; - -export const AgentSelector = ({ - flowSpec, - setFlowSpec, -}: { - flowSpec: IAgentFlowSpec | null; - setFlowSpec: (agent: IAgentFlowSpec | null) => void; -}) => { - const [isModalVisible, setIsModalVisible] = useState(false); - - return ( -
-
setIsModalVisible(true)} - className="hover:bg-secondary h-full duration-300 border border-dashed rounded p-2" - > - {flowSpec && ( -
- {flowSpec.type === "groupchat" ? ( - - ) : ( - - )} - {flowSpec.config.name} -
- {" "} - {flowSpec.config.description || flowSpec.config.name} -
-
- {" "} - - {(flowSpec.skills && flowSpec.skills?.length) || 0} skills - - - | max replies: {flowSpec.config.max_consecutive_auto_reply} - -
-
- )} -
- { - <> - { - setFlowSpec(agent); - }} - /> - - } -
- ); -}; -export const FlowConfigViewer = ({ - flowConfig, - setFlowConfig, -}: { - flowConfig: IFlowConfig; - setFlowConfig: (newFlowConfig: IFlowConfig) => void; -}) => { - // Local state for sender and receiver FlowSpecs - const [senderFlowSpec, setSenderFlowSpec] = - React.useState(flowConfig.sender); - - const [localFlowConfig, setLocalFlowConfig] = - React.useState(flowConfig); - - const [receiverFlowSpec, setReceiverFlowSpec] = - React.useState(flowConfig.receiver); - - // Update the local state and propagate changes to the parent component - const updateSenderFlowSpec = (newFlowSpec: IAgentFlowSpec | null) => { - setSenderFlowSpec(newFlowSpec); - if (newFlowSpec) { - setFlowConfig({ ...flowConfig, sender: newFlowSpec }); - } - }; - - const updateReceiverFlowSpec = (newFlowSpec: IAgentFlowSpec | null) => { - setReceiverFlowSpec(newFlowSpec); - if (newFlowSpec) { - setFlowConfig({ ...flowConfig, receiver: newFlowSpec }); - } - }; - - const updateFlowConfig = (key: string, value: string) => { - // When an updatedFlowConfig is created using localFlowConfig, if the contents of FlowConfigViewer Modal are changed after the Agent Specification Modal is updated, the updated contents of the Agent Specification Modal are not saved. Fixed to localFlowConfig->flowConfig. Fixed a bug. - const updatedFlowConfig = { ...flowConfig, [key]: value }; - console.log("updatedFlowConfig: ", updatedFlowConfig); - setLocalFlowConfig(updatedFlowConfig); - setFlowConfig(updatedFlowConfig); - }; - - return ( - <> - {/*
{flowConfig.name}
*/} - updateFlowConfig("name", e.target.value)} - /> - } - /> - - updateFlowConfig("description", e.target.value)} - /> - } - /> - - updateFlowConfig("summary_method", value)} - options={ - [ - { label: "last", value: "last" }, - { label: "none", value: "none" }, - { label: "llm", value: "llm" }, - ] as any - } - /> - } - /> -
-
-
Sender
- -
-
-
Receiver
- -
-
- - ); -}; - export const MonacoEditor = ({ value, editorRef, diff --git a/samples/apps/autogen-studio/frontend/src/components/header.tsx b/samples/apps/autogen-studio/frontend/src/components/header.tsx index 8ec85326923..d0adf2e0a3a 100644 --- a/samples/apps/autogen-studio/frontend/src/components/header.tsx +++ b/samples/apps/autogen-studio/frontend/src/components/header.tsx @@ -25,7 +25,7 @@ const Header = ({ meta, link }: any) => { const links: any[] = [ { name: "Build", href: "/build" }, { name: "Playground", href: "/" }, - { name: "Gallery", href: "/gallery" }, + // { name: "Gallery", href: "/gallery" }, // { name: "Data Explorer", href: "/explorer" }, ]; diff --git a/samples/apps/autogen-studio/frontend/src/components/types.ts b/samples/apps/autogen-studio/frontend/src/components/types.ts index 522682a4884..eba39144602 100644 --- a/samples/apps/autogen-studio/frontend/src/components/types.ts +++ b/samples/apps/autogen-studio/frontend/src/components/types.ts @@ -2,14 +2,13 @@ export type NotificationType = "success" | "info" | "warning" | "error"; export interface IMessage { user_id: string; - root_msg_id: string; - msg_id?: string; role: string; content: string; - timestamp?: string; - personalize?: boolean; - ra?: string; - session_id?: string; + created_at?: string; + updated_at?: string; + session_id?: number; + connection_id?: string; + workflow_id?: number; } export interface IStatus { @@ -21,7 +20,7 @@ export interface IStatus { export interface IChatMessage { text: string; sender: "user" | "bot"; - metadata?: any; + meta?: any; msg_id: string; } @@ -30,6 +29,7 @@ export interface ILLMConfig { timeout?: number; cache_seed?: number | null; temperature: number; + max_tokens: number; } export interface IAgentConfig { @@ -40,47 +40,36 @@ export interface IAgentConfig { system_message: string | ""; is_termination_msg?: boolean | string; default_auto_reply?: string | null; - code_execution_config?: boolean | string | { [key: string]: any } | null; + code_execution_config?: "none" | "local" | "docker"; description?: string; -} -export interface IAgentFlowSpec { - type: "assistant" | "userproxy" | "groupchat"; - config: IAgentConfig; - timestamp?: string; - id?: string; - skills?: Array; - user_id?: string; + admin_name?: string; + messages?: Array; + max_round?: number; + speaker_selection_method?: string; + allow_repeat_speaker?: boolean; } -export interface IGroupChatConfig { - agents: Array; - admin_name: string; - messages: Array; - max_round: number; - speaker_selection_method: "auto" | "round_robin" | "random"; - allow_repeat_speaker: boolean | Array; -} - -export interface IGroupChatFlowSpec { - type: "groupchat"; +export interface IAgent { + type?: "assistant" | "userproxy" | "groupchat"; config: IAgentConfig; - groupchat_config: IGroupChatConfig; - id?: string; - timestamp?: string; + created_at?: string; + updated_at?: string; + id?: number; + skills?: Array; user_id?: string; - description?: string; } -export interface IFlowConfig { +export interface IWorkflow { name: string; description: string; - sender: IAgentFlowSpec; - receiver: IAgentFlowSpec | IGroupChatFlowSpec; + sender: IAgent; + receiver: IAgent; type: "twoagents" | "groupchat"; - timestamp?: string; + created_at?: string; + updated_at?: string; summary_method?: "none" | "last" | "llm"; - id?: string; + id?: number; user_id?: string; } @@ -89,11 +78,12 @@ export interface IModelConfig { api_key?: string; api_version?: string; base_url?: string; - api_type?: string; + api_type?: "open_ai" | "azure" | "google"; user_id?: string; - timestamp?: string; + created_at?: string; + updated_at?: string; description?: string; - id?: string; + id?: number; } export interface IMetadataFile { @@ -105,27 +95,29 @@ export interface IMetadataFile { } export interface IChatSession { - id: string; + id?: number; user_id: string; - timestamp: string; - flow_config: IFlowConfig; + workflow_id?: number; + created_at?: string; + updated_at?: string; name: string; } export interface IGalleryItem { - id: string; + id: number; messages: Array; session: IChatSession; tags: Array; - timestamp: string; + created_at: string; + updated_at: string; } export interface ISkill { - title: string; - file_name?: string; + name: string; content: string; - id?: string; - timestamp?: string; + id?: number; description?: string; user_id?: string; + created_at?: string; + updated_at?: string; } diff --git a/samples/apps/autogen-studio/frontend/src/components/utils.ts b/samples/apps/autogen-studio/frontend/src/components/utils.ts index 73b9f42207c..2264f5c66a2 100644 --- a/samples/apps/autogen-studio/frontend/src/components/utils.ts +++ b/samples/apps/autogen-studio/frontend/src/components/utils.ts @@ -1,12 +1,11 @@ import { + IAgent, IAgentConfig, - IAgentFlowSpec, - IFlowConfig, - IGroupChatFlowSpec, ILLMConfig, IModelConfig, ISkill, IStatus, + IWorkflow, } from "./types"; export const getServerUrl = () => { @@ -66,7 +65,8 @@ export function fetchJSON( url: string | URL, payload: any = {}, onSuccess: (data: any) => void, - onError: (error: IStatus) => void + onError: (error: IStatus) => void, + onFinal: () => void = () => {} ) { return fetch(url, payload) .then(function (response) { @@ -95,6 +95,9 @@ export function fetchJSON( status: false, message: `There was an error connecting to server. (${err}) `, }); + }) + .finally(() => { + onFinal(); }); } export const capitalize = (s: string) => { @@ -243,60 +246,138 @@ export const formatDuration = (seconds: number) => { return parts.length > 0 ? parts.join(" ") : "0 sec"; }; -export const sampleAgentConfig = (user_id: string = "guestuser@gmail.com") => { - const sampleAgent: IAgentFlowSpec = { +export const sampleModelConfig = (modelType: string = "open_ai") => { + const openaiConfig: IModelConfig = { + model: "gpt-4-1106-preview", + api_type: "open_ai", + description: "OpenAI GPT-4 model", + }; + const azureConfig: IModelConfig = { + model: "gpt-4", + api_type: "azure", + api_version: "v1", + base_url: "https://youazureendpoint.azure.com/", + description: "Azure model", + }; + + const googleConfig: IModelConfig = { + model: "gemini-1.0-pro", + api_type: "google", + description: "Google Gemini Model model", + }; + + switch (modelType) { + case "open_ai": + return openaiConfig; + case "azure": + return azureConfig; + case "google": + return googleConfig; + default: + return openaiConfig; + } +}; + +export const getRandomIntFromDateAndSalt = (salt: number = 43444) => { + const currentDate = new Date(); + const seed = currentDate.getTime() + salt; + const randomValue = Math.sin(seed) * 10000; + const randomInt = Math.floor(randomValue) % 100; + return randomInt; +}; + +export const sampleAgentConfig = (agent_type: string = "assistant") => { + const llm_config: ILLMConfig = { + config_list: [], + temperature: 0.1, + timeout: 600, + cache_seed: null, + max_tokens: 1000, + }; + + const userProxyConfig: IAgentConfig = { + name: "userproxy", + human_input_mode: "NEVER", + description: "User Proxy", + max_consecutive_auto_reply: 25, + system_message: "You are a helpful assistant.", + default_auto_reply: "TERMINATE", + llm_config: false, + code_execution_config: "local", + }; + const userProxyFlowSpec: IAgent = { + type: "userproxy", + config: userProxyConfig, + }; + + const assistantConfig: IAgentConfig = { + name: "primary_assistant", + description: "Primary Assistant", + llm_config: llm_config, + human_input_mode: "NEVER", + max_consecutive_auto_reply: 25, + code_execution_config: "none", + system_message: + "You are a helpful AI assistant. Solve tasks using your coding and language skills. In the following cases, suggest python code (in a python coding block) or shell script (in a sh coding block) for the user to execute. 1. When you need to collect info, use the code to output the info you need, for example, browse or search the web, download/read a file, print the content of a webpage or a file, get the current date/time, check the operating system. After sufficient info is printed and the task is ready to be solved based on your language skill, you can solve the task by yourself. 2. When you need to perform some task with code, use the code to perform the task and output the result. Finish the task smartly. Solve the task step by step if you need to. If a plan is not provided, explain your plan first. Be clear which step uses code, and which step uses your language skill. When using code, you must indicate the script type in the code block. The user cannot provide any other feedback or perform any other action beyond executing the code you suggest. The user can't modify your code. So do not suggest incomplete code which requires users to modify. Don't use a code block if it's not intended to be executed by the user. If you want the user to save the code in a file before executing it, put # filename: inside the code block as the first line. Don't include multiple code blocks in one response. Do not ask users to copy and paste the result. Instead, use 'print' function for the output when relevant. Check the execution result returned by the user. If the result indicates there is an error, fix the error and output the code again. Suggest the full code instead of partial code or code changes. If the error can't be fixed or if the task is not solved even after the code is executed successfully, analyze the problem, revisit your assumption, collect additional info you need, and think of a different approach to try. When you find an answer, verify the answer carefully. Include verifiable evidence in your response if possible. Reply 'TERMINATE' in the end when everything is done.", + }; + + const assistantFlowSpec: IAgent = { type: "assistant", - user_id: user_id, - config: { - name: "sample_assistant", - description: "Sample assistant", - llm_config: { - config_list: [ - { - model: "gpt-4-1106-preview", - }, - ], - temperature: 0.1, - timeout: 600, - cache_seed: null, - }, - human_input_mode: "NEVER", - code_execution_config: false, - max_consecutive_auto_reply: 8, - system_message: - "You are a helpful AI assistant. Solve tasks using your coding and language skills. In the following cases, suggest python code (in a python coding block) or shell script (in a sh coding block) for the user to execute. 1. When you need to collect info, use the code to output the info you need, for example, browse or search the web, download/read a file, print the content of a webpage or a file, get the current date/time, check the operating system. After sufficient info is printed and the task is ready to be solved based on your language skill, you can solve the task by yourself. 2. When you need to perform some task with code, use the code to perform the task and output the result. Finish the task smartly. Solve the task step by step if you need to. If a plan is not provided, explain your plan first. Be clear which step uses code, and which step uses your language skill. When using code, you must indicate the script type in the code block. The user cannot provide any other feedback or perform any other action beyond executing the code you suggest. The user can't modify your code. So do not suggest incomplete code which requires users to modify. Don't use a code block if it's not intended to be executed by the user. If you want the user to save the code in a file before executing it, put # filename: inside the code block as the first line. Don't include multiple code blocks in one response. Do not ask users to copy and paste the result. Instead, use 'print' function for the output when relevant. Check the execution result returned by the user. If the result indicates there is an error, fix the error and output the code again. Suggest the full code instead of partial code or code changes. If the error can't be fixed or if the task is not solved even after the code is executed successfully, analyze the problem, revisit your assumption, collect additional info you need, and think of a different approach to try. When you find an answer, verify the answer carefully. Include verifiable evidence in your response if possible. Reply 'TERMINATE' in the end when everything is done.", + config: assistantConfig, + }; + + const groupChatAssistantConfig = Object.assign( + { + admin_name: "groupchat_assistant", + messages: [], + max_round: 10, + speaker_selection_method: "auto", + allow_repeat_speaker: false, }, + assistantConfig + ); + groupChatAssistantConfig.name = "groupchat_assistant"; + groupChatAssistantConfig.system_message = + "You are a helpful assistant skilled at cordinating a group of other assistants to solve a task. "; + groupChatAssistantConfig.description = "Group Chat Assistant"; + + const groupChatFlowSpec: IAgent = { + type: "groupchat", + config: groupChatAssistantConfig, }; - return sampleAgent; + + if (agent_type === "userproxy") { + return userProxyFlowSpec; + } else if (agent_type === "assistant") { + return assistantFlowSpec; + } else if (agent_type === "groupchat") { + return groupChatFlowSpec; + } else { + return assistantFlowSpec; + } }; export const sampleWorkflowConfig = (type = "twoagents") => { - const llm_model_config: IModelConfig[] = [ - { - model: "gpt-4-1106-preview", - }, - ]; + const llm_model_config: IModelConfig[] = []; const llm_config: ILLMConfig = { config_list: llm_model_config, temperature: 0.1, timeout: 600, cache_seed: null, + max_tokens: 1000, }; const userProxyConfig: IAgentConfig = { name: "userproxy", human_input_mode: "NEVER", - max_consecutive_auto_reply: 5, + max_consecutive_auto_reply: 15, system_message: "You are a helpful assistant.", default_auto_reply: "TERMINATE", llm_config: false, - code_execution_config: { - work_dir: null, - use_docker: false, - }, + code_execution_config: "local", }; - const userProxyFlowSpec: IAgentFlowSpec = { + const userProxyFlowSpec: IAgent = { type: "userproxy", config: userProxyConfig, }; @@ -306,17 +387,17 @@ export const sampleWorkflowConfig = (type = "twoagents") => { llm_config: llm_config, human_input_mode: "NEVER", max_consecutive_auto_reply: 8, - code_execution_config: false, + code_execution_config: "none", system_message: "You are a helpful AI assistant. Solve tasks using your coding and language skills. In the following cases, suggest python code (in a python coding block) or shell script (in a sh coding block) for the user to execute. 1. When you need to collect info, use the code to output the info you need, for example, browse or search the web, download/read a file, print the content of a webpage or a file, get the current date/time, check the operating system. After sufficient info is printed and the task is ready to be solved based on your language skill, you can solve the task by yourself. 2. When you need to perform some task with code, use the code to perform the task and output the result. Finish the task smartly. Solve the task step by step if you need to. If a plan is not provided, explain your plan first. Be clear which step uses code, and which step uses your language skill. When using code, you must indicate the script type in the code block. The user cannot provide any other feedback or perform any other action beyond executing the code you suggest. The user can't modify your code. So do not suggest incomplete code which requires users to modify. Don't use a code block if it's not intended to be executed by the user. If you want the user to save the code in a file before executing it, put # filename: inside the code block as the first line. Don't include multiple code blocks in one response. Do not ask users to copy and paste the result. Instead, use 'print' function for the output when relevant. Check the execution result returned by the user. If the result indicates there is an error, fix the error and output the code again. Suggest the full code instead of partial code or code changes. If the error can't be fixed or if the task is not solved even after the code is executed successfully, analyze the problem, revisit your assumption, collect additional info you need, and think of a different approach to try. When you find an answer, verify the answer carefully. Include verifiable evidence in your response if possible. Reply 'TERMINATE' in the end when everything is done.", }; - const assistantFlowSpec: IAgentFlowSpec = { + const assistantFlowSpec: IAgent = { type: "assistant", config: assistantConfig, }; - const workFlowConfig: IFlowConfig = { + const workFlowConfig: IWorkflow = { name: "Default Agent Workflow", description: "Default Agent Workflow", sender: userProxyFlowSpec, @@ -324,26 +405,27 @@ export const sampleWorkflowConfig = (type = "twoagents") => { type: "twoagents", }; - const groupChatAssistantConfig = Object.assign({}, assistantConfig); - groupChatAssistantConfig.name = "groupchat_assistant"; - groupChatAssistantConfig.system_message = - "You are a helpful assistant skilled at cordinating a group of other assistants to solve a task. "; - - const groupChatFlowSpec: IGroupChatFlowSpec = { - type: "groupchat", - config: groupChatAssistantConfig, - groupchat_config: { - agents: [assistantFlowSpec, assistantFlowSpec], + const groupChatAssistantConfig = Object.assign( + { admin_name: "groupchat_assistant", messages: [], max_round: 10, speaker_selection_method: "auto", allow_repeat_speaker: false, + description: "Group Chat Assistant", }, - description: "Default Group Workflow", + assistantConfig + ); + groupChatAssistantConfig.name = "groupchat_assistant"; + groupChatAssistantConfig.system_message = + "You are a helpful assistant skilled at cordinating a group of other assistants to solve a task. "; + + const groupChatFlowSpec: IAgent = { + type: "groupchat", + config: groupChatAssistantConfig, }; - const groupChatWorkFlowConfig: IFlowConfig = { + const groupChatWorkFlowConfig: IWorkflow = { name: "Default Group Workflow", description: "Default Group Workflow", sender: userProxyFlowSpec, @@ -359,79 +441,72 @@ export const sampleWorkflowConfig = (type = "twoagents") => { return workFlowConfig; }; -export const getModels = () => { - const models = [ - { - model: "gpt-4-1106-preview", - }, - { - model: "gpt-3.5-turbo-16k", - }, - { - model: "TheBloke/zephyr-7B-alpha-AWQ", - base_url: "http://localhost:8000/v1", - }, - ]; - return models; -}; - export const getSampleSkill = () => { const content = ` - ## This is a sample skill. Replace with your own skill function - ## In general, a good skill must have 3 sections: - ## 1. Imports (import libraries needed for your skill) - ## 2. Function definition AND docstrings (this helps the LLM understand what the function does and how to use it) - ## 3. Function body (the actual code that implements the function) - - import numpy as np - import matplotlib.pyplot as plt - from matplotlib import font_manager as fm - - def save_cat_ascii_art_to_png(filename='ascii_cat.png'): - """ - Creates ASCII art of a cat and saves it to a PNG file. - - :param filename: str, the name of the PNG file to save the ASCII art. - """ - # ASCII art string - cat_art = [ - " /\_/\ ", - " ( o.o ) ", - " > ^ < " - ] - - # Determine shape of output array - height = len(cat_art) - width = max(len(line) for line in cat_art) - - # Create a figure and axis to display ASCII art - fig, ax = plt.subplots(figsize=(width, height)) - ax.axis('off') # Hide axes - - # Get a monospace font - prop = fm.FontProperties(family='monospace') - - # Display ASCII art using text - for y, line in enumerate(cat_art): - ax.text(0, height-y-1, line, fontproperties=prop, fontsize=12) - - # Adjust layout - plt.tight_layout() - - # Save figure to file - plt.savefig(filename, dpi=120, bbox_inches='tight', pad_inches=0.1) - plt.close(fig)`; +from typing import List +import uuid +import requests # to perform HTTP requests +from pathlib import Path + +from openai import OpenAI + + +def generate_and_save_images(query: str, image_size: str = "1024x1024") -> List[str]: + """ + Function to paint, draw or illustrate images based on the users query or request. Generates images from a given query using OpenAI's DALL-E model and saves them to disk. Use the code below anytime there is a request to create an image. + + :param query: A natural language description of the image to be generated. + :param image_size: The size of the image to be generated. (default is "1024x1024") + :return: A list of filenames for the saved images. + """ + + client = OpenAI() # Initialize the OpenAI client + response = client.images.generate(model="dall-e-3", prompt=query, n=1, size=image_size) # Generate images + + # List to store the file names of saved images + saved_files = [] + + # Check if the response is successful + if response.data: + for image_data in response.data: + # Generate a random UUID as the file name + file_name = str(uuid.uuid4()) + ".png" # Assuming the image is a PNG + file_path = Path(file_name) + + img_url = image_data.url + img_response = requests.get(img_url) + if img_response.status_code == 200: + # Write the binary content to a file + with open(file_path, "wb") as img_file: + img_file.write(img_response.content) + print(f"Image saved to {file_path}") + saved_files.append(str(file_path)) + else: + print(f"Failed to download the image from {img_url}") + else: + print("No image data found in the response!") + + # Return the list of saved files + return saved_files + + +# Example usage of the function: +# generate_and_save_images("A cute baby sea otter") + `; const skill: ISkill = { - title: "save_cat_ascii_art_to_png", - description: "save cat ascii art to png", + name: "generate_images", + description: "Generate and save images based on a user's query.", content: content, }; return skill; }; -export const timeAgo = (dateString: string): string => { +export const timeAgo = ( + dateString: string, + returnFormatted: boolean = false +): string => { // if dateStr is empty, return empty string if (!dateString) { return ""; @@ -454,10 +529,20 @@ export const timeAgo = (dateString: string): string => { const minutesAgo = Math.floor(timeDifference / (1000 * 60)); const hoursAgo = Math.floor(minutesAgo / 60); - // Format the date into a readable format e.g. "November 27" - const options: Intl.DateTimeFormatOptions = { month: "long", day: "numeric" }; + // Format the date into a readable format e.g. "November 27, 2021, 3:45 PM" + const options: Intl.DateTimeFormatOptions = { + month: "long", + day: "numeric", + year: "numeric", + hour: "numeric", + minute: "numeric", + }; const formattedDate = timestamp.toLocaleDateString(undefined, options); + if (returnFormatted) { + return formattedDate; + } + // Determine the time difference string let timeAgoStr: string; if (minutesAgo < 1) { @@ -527,7 +612,7 @@ export const fetchVersion = () => { */ export const sanitizeConfig = ( data: any, - keys: string[] = ["api_key", "id"] + keys: string[] = ["api_key", "id", "created_at", "updated_at"] ): any => { if (Array.isArray(data)) { return data.map((item) => sanitizeConfig(item, keys)); diff --git a/samples/apps/autogen-studio/frontend/src/components/views/builder/agents.tsx b/samples/apps/autogen-studio/frontend/src/components/views/builder/agents.tsx index be8a30f7247..8800ebfbdd3 100644 --- a/samples/apps/autogen-studio/frontend/src/components/views/builder/agents.tsx +++ b/samples/apps/autogen-studio/frontend/src/components/views/builder/agents.tsx @@ -8,24 +8,17 @@ import { } from "@heroicons/react/24/outline"; import { Dropdown, MenuProps, Modal, message } from "antd"; import * as React from "react"; -import { IAgentFlowSpec, IStatus } from "../../types"; +import { IAgent, IStatus } from "../../types"; import { appContext } from "../../../hooks/provider"; import { fetchJSON, getServerUrl, - sampleAgentConfig, sanitizeConfig, timeAgo, truncateText, } from "../../utils"; -import { - AgentFlowSpecView, - BounceLoader, - Card, - CardHoverBar, - LaunchButton, - LoadingOverlay, -} from "../../atoms"; +import { BounceLoader, Card, CardHoverBar, LoadingOverlay } from "../../atoms"; +import { AgentViewer } from "./utils/agentconfig"; const AgentsView = ({}: any) => { const [loading, setLoading] = React.useState(false); @@ -37,25 +30,30 @@ const AgentsView = ({}: any) => { const { user } = React.useContext(appContext); const serverUrl = getServerUrl(); const listAgentsUrl = `${serverUrl}/agents?user_id=${user?.email}`; - const saveAgentsUrl = `${serverUrl}/agents`; - const deleteAgentUrl = `${serverUrl}/agents/delete`; - const [agents, setAgents] = React.useState([]); - const [selectedAgent, setSelectedAgent] = - React.useState(null); + const [agents, setAgents] = React.useState([]); + const [selectedAgent, setSelectedAgent] = React.useState(null); const [showNewAgentModal, setShowNewAgentModal] = React.useState(false); const [showAgentModal, setShowAgentModal] = React.useState(false); - const sampleAgent = sampleAgentConfig(user?.email || ""); - const [newAgent, setNewAgent] = React.useState( - sampleAgent - ); + const sampleAgent = { + config: { + name: "sample_agent", + description: "Sample agent description", + human_input_mode: "NEVER", + max_consecutive_auto_reply: 3, + system_message: "", + }, + }; + const [newAgent, setNewAgent] = React.useState(sampleAgent); - const deleteAgent = (agent: IAgentFlowSpec) => { + const deleteAgent = (agent: IAgent) => { setError(null); setLoading(true); + + const deleteAgentUrl = `${serverUrl}/agents/delete?user_id=${user?.email}&agent_id=${agent.id}`; // const fetch; const payLoad = { method: "DELETE", @@ -71,8 +69,7 @@ const AgentsView = ({}: any) => { const onSuccess = (data: any) => { if (data && data.status) { message.success(data.message); - console.log("agents", data.data); - setAgents(data.data); + fetchAgents(); } else { message.error(data.message); } @@ -98,8 +95,6 @@ const AgentsView = ({}: any) => { const onSuccess = (data: any) => { if (data && data.status) { - // message.success(data.message); - setAgents(data.data); } else { message.error(data.message); @@ -114,42 +109,6 @@ const AgentsView = ({}: any) => { fetchJSON(listAgentsUrl, payLoad, onSuccess, onError); }; - const saveAgent = (agent: IAgentFlowSpec) => { - setError(null); - setLoading(true); - // const fetch; - - const payLoad = { - method: "POST", - headers: { - Accept: "application/json", - "Content-Type": "application/json", - }, - body: JSON.stringify({ - user_id: user?.email, - agent: agent, - }), - }; - - const onSuccess = (data: any) => { - if (data && data.status) { - message.success(data.message); - // console.log("agents", data.data); - setAgents(data.data); - } else { - message.error(data.message); - } - setLoading(false); - setNewAgent(sampleAgent); - }; - const onError = (err: any) => { - setError(err); - message.error(err.message); - setLoading(false); - }; - fetchJSON(saveAgentsUrl, payLoad, onSuccess, onError); - }; - React.useEffect(() => { if (user) { // console.log("fetching messages", messages); @@ -157,7 +116,7 @@ const AgentsView = ({}: any) => { } }, []); - const agentRows = (agents || []).map((agent: IAgentFlowSpec, i: number) => { + const agentRows = (agents || []).map((agent: IAgent, i: number) => { const cardItems = [ { title: "Download", @@ -185,11 +144,10 @@ const AgentsView = ({}: any) => { let newAgent = { ...agent }; newAgent.config.name = `${agent.config.name}_copy`; newAgent.user_id = user?.email; - newAgent.timestamp = new Date().toISOString(); + newAgent.updated_at = new Date().toISOString(); if (newAgent.id) { delete newAgent.id; } - setNewAgent(newAgent); setShowNewAgentModal(true); }, @@ -206,27 +164,41 @@ const AgentsView = ({}: any) => { }, ]; return ( -
-
- {truncateText(agent.config.name, 25)}
- } - onClick={() => { - setSelectedAgent(agent); - setShowAgentModal(true); - }} - > -
- {" "} - {truncateText(agent.config.description || "", 70)} +
  • + + {truncateText(agent.config.name || "", 25)}
  • -
    {timeAgo(agent.timestamp || "")}
    - - -
    -
    + } + onClick={() => { + setSelectedAgent(agent); + setShowAgentModal(true); + }} + > + +
    + {timeAgo(agent.updated_at || "")} +
    + + + ); }); @@ -237,45 +209,39 @@ const AgentsView = ({}: any) => { setShowAgentModal, handler, }: { - agent: IAgentFlowSpec | null; - setAgent: (agent: IAgentFlowSpec | null) => void; + agent: IAgent | null; + setAgent: (agent: IAgent | null) => void; showAgentModal: boolean; setShowAgentModal: (show: boolean) => void; - handler?: (agent: IAgentFlowSpec | null) => void; + handler?: (agent: IAgent | null) => void; }) => { - const [localAgent, setLocalAgent] = React.useState( - agent - ); + const [localAgent, setLocalAgent] = React.useState(agent); + + const closeModal = () => { + setShowAgentModal(false); + if (handler) { + handler(localAgent); + } + }; return ( - Agent Specification{" "} - - {agent?.config?.name || ""} - {" "} - - } + title={<>Agent Configuration} width={800} open={showAgentModal} onOk={() => { - setAgent(null); - setShowAgentModal(false); - if (handler) { - handler(localAgent); - } + closeModal(); }} onCancel={() => { - setAgent(null); - setShowAgentModal(false); + closeModal(); }} + footer={[]} > {agent && ( - )} {/* {JSON.stringify(localAgent)} */} @@ -344,10 +310,8 @@ const AgentsView = ({}: any) => { setAgent={setSelectedAgent} setShowAgentModal={setShowAgentModal} showAgentModal={showAgentModal} - handler={(agent: IAgentFlowSpec | null) => { - if (agent) { - saveAgent(agent); - } + handler={(agent: IAgent | null) => { + fetchAgents(); }} /> @@ -356,10 +320,8 @@ const AgentsView = ({}: any) => { setAgent={setNewAgent} setShowAgentModal={setShowNewAgentModal} showAgentModal={showNewAgentModal} - handler={(agent: IAgentFlowSpec | null) => { - if (agent) { - saveAgent(agent); - } + handler={(agent: IAgent | null) => { + fetchAgents(); }} /> @@ -397,7 +359,7 @@ const AgentsView = ({}: any) => { {agents && agents.length > 0 && (
    -
    {agentRows}
    +
      {agentRows}
    )} diff --git a/samples/apps/autogen-studio/frontend/src/components/views/builder/models.tsx b/samples/apps/autogen-studio/frontend/src/components/views/builder/models.tsx index be2c11099e3..2a3b0506d79 100644 --- a/samples/apps/autogen-studio/frontend/src/components/views/builder/models.tsx +++ b/samples/apps/autogen-studio/frontend/src/components/views/builder/models.tsx @@ -2,7 +2,6 @@ import { ArrowDownTrayIcon, ArrowUpTrayIcon, DocumentDuplicateIcon, - ExclamationTriangleIcon, InformationCircleIcon, PlusIcon, TrashIcon, @@ -18,8 +17,15 @@ import { timeAgo, truncateText, } from "../../utils"; -import { BounceLoader, Card, CardHoverBar, LoadingOverlay } from "../../atoms"; +import { + BounceLoader, + Card, + CardHoverBar, + ControlRowView, + LoadingOverlay, +} from "../../atoms"; import TextArea from "antd/es/input/TextArea"; +import { ModelConfigView } from "./utils/modelconfig"; const ModelsView = ({}: any) => { const [loading, setLoading] = React.useState(false); @@ -31,8 +37,7 @@ const ModelsView = ({}: any) => { const { user } = React.useContext(appContext); const serverUrl = getServerUrl(); const listModelsUrl = `${serverUrl}/models?user_id=${user?.email}`; - const saveModelsUrl = `${serverUrl}/models`; - const deleteModelUrl = `${serverUrl}/models/delete`; + const createModelUrl = `${serverUrl}/models`; const testModelUrl = `${serverUrl}/models/test`; const defaultModel: IModelConfig = { @@ -50,28 +55,23 @@ const ModelsView = ({}: any) => { ); const [showNewModelModal, setShowNewModelModal] = React.useState(false); - const [showModelModal, setShowModelModal] = React.useState(false); const deleteModel = (model: IModelConfig) => { setError(null); setLoading(true); - // const fetch; + const deleteModelUrl = `${serverUrl}/models/delete?user_id=${user?.email}&model_id=${model.id}`; const payLoad = { method: "DELETE", headers: { "Content-Type": "application/json", }, - body: JSON.stringify({ - user_id: user?.email, - model: model, - }), }; const onSuccess = (data: any) => { if (data && data.status) { message.success(data.message); - setModels(data.data); + fetchModels(); } else { message.error(data.message); } @@ -111,9 +111,10 @@ const ModelsView = ({}: any) => { fetchJSON(listModelsUrl, payLoad, onSuccess, onError); }; - const saveModel = (model: IModelConfig) => { + const createModel = (model: IModelConfig) => { setError(null); setLoading(true); + model.user_id = user?.email; const payLoad = { method: "POST", @@ -121,17 +122,14 @@ const ModelsView = ({}: any) => { Accept: "application/json", "Content-Type": "application/json", }, - body: JSON.stringify({ - user_id: user?.email, - model: model, - }), + body: JSON.stringify(model), }; const onSuccess = (data: any) => { if (data && data.status) { message.success(data.message); - // console.log("models", data.data); - setModels(data.data); + const updatedModels = [data.data].concat(models || []); + setModels(updatedModels); } else { message.error(data.message); } @@ -142,7 +140,7 @@ const ModelsView = ({}: any) => { message.error(err.message); setLoading(false); }; - fetchJSON(saveModelsUrl, payLoad, onSuccess, onError); + fetchJSON(createModelUrl, payLoad, onSuccess, onError); }; React.useEffect(() => { @@ -180,7 +178,7 @@ const ModelsView = ({}: any) => { let newModel = { ...model }; newModel.model = `${model.model} Copy`; newModel.user_id = user?.email; - newModel.timestamp = new Date().toISOString(); + newModel.updated_at = new Date().toISOString(); if (newModel.id) { delete newModel.id; } @@ -200,27 +198,35 @@ const ModelsView = ({}: any) => { }, ]; return ( -
    -
    - {truncateText(model.model || "", 20)}
    - } - onClick={() => { - setSelectedModel(model); - setShowModelModal(true); - }} +
  • + {truncateText(model.model || "", 20)}
  • + } + onClick={() => { + setSelectedModel(model); + setShowModelModal(true); + }} + > +
    + {" "} + {truncateText(model.description || model.model || "", 70)} +
    +
    -
    - {" "} - {truncateText(model.description || model.model || "", 70)} -
    -
    {timeAgo(model.timestamp || "")}
    - - -
    -
    + {timeAgo(model.updated_at || "")} +
    + + + ); }); @@ -231,47 +237,20 @@ const ModelsView = ({}: any) => { setShowModelModal, handler, }: { - model: IModelConfig | null; + model: IModelConfig; setModel: (model: IModelConfig | null) => void; showModelModal: boolean; setShowModelModal: (show: boolean) => void; handler?: (agent: IModelConfig) => void; }) => { - const [loadingModelTest, setLoadingModelTest] = React.useState(false); - const [modelStatus, setModelStatus] = React.useState(null); - - const [localModel, setLocalModel] = React.useState( - model - ); - const testModel = (model: IModelConfig) => { - setModelStatus(null); - setLoadingModelTest(true); - const payLoad = { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - user_id: user?.email, - model: model, - }), - }; + const [localModel, setLocalModel] = React.useState(model); - const onSuccess = (data: any) => { - if (data && data.status) { - message.success(data.message); - setModelStatus(data.data); - } else { - message.error(data.message); - } - setLoadingModelTest(false); - setModelStatus(data); - }; - const onError = (err: any) => { - message.error(err.message); - setLoadingModelTest(false); - }; - fetchJSON(testModelUrl, payLoad, onSuccess, onError); + const closeModal = () => { + setModel(null); + setShowModelModal(false); + if (handler) { + handler(model); + } }; return ( @@ -284,137 +263,21 @@ const ModelsView = ({}: any) => { } width={800} open={showModelModal} - footer={[ - , - , - , - ]} + footer={[]} onOk={() => { - setModel(null); - setShowModelModal(false); - if (handler) { - if (localModel) { - handler(localModel); - } - } + closeModal(); }} onCancel={() => { - setModel(null); - setShowModelModal(false); + closeModal(); }} > -
    -
    Enter parameters for your model.
    - { - setLocalModel({ ...localModel, model: e.target.value }); - }} - /> - { - if (localModel) { - setLocalModel({ ...localModel, api_key: e.target.value }); - } - }} - /> - { - if (localModel) { - setLocalModel({ ...localModel, base_url: e.target.value }); - } - }} - /> - { - if (localModel) { - setLocalModel({ ...localModel, api_type: e.target.value }); - } - }} + {model && ( + - { - if (localModel) { - setLocalModel({ ...localModel, api_version: e.target.value }); - } - }} - /> -