ronantakizawa/codereview-bench huggingface dataset:
Repository: emcie-co/parlant
File: server/tests/api/test_sessions.py (Python)

Code before:
            "agent_id": agent_id,
            "title": "Test Session2 Title",
        },
    )

    response = client.get(f"/sessions/?agent_id={agent_id}")

    assert response.status_code == status.HTTP_200_OK
    data = response.json()

    assert data

    sessions = data["sessions"]

    assert len(sessions) == 3

    assert sessions[0]["title"] == "Test Session1 Title"
    assert sessions[1]["title"] == "Test Session2 Title"
    assert sessions[2]["title"] == "Test Session2 Title"

    assert sessions[0]["end_user_id"] == "test_user1"
    assert sessions[1]["end_user_id"] == "test_user2"
    assert sessions[2]["end_user_id"] == "test_user2"


def test_that_sessions_can_be_listed(
    client: TestClient,
    agent_id: AgentId,
) -> None:
    client.post(
        "/sessions",
        json={
            "end_user_id": "test_user1",
            "agent_id": agent_id,
            "title": "Test Session1 Title",
        },
    )

    client.post(
        "/sessions",
        json={
            "end_user_id": "test_user2",
            "agent_id": agent_id,
            "title": "Test Session2 Title",
        },
    )

    client.post(
        "/sessions",
        json={
            "end_user_id": "test_user2",

Review comment (refactor):
1. Is it just me or is this test duplicated?
2. I think you're not testing the use case where an agent ID is not provided
3. I think if in the backend you completely ignore the agent ID provided, your tests will still pass. This is because as far as I can see you're not testing the filtering logic with multiple agent IDs

Code after:
        json={
            "end_user_id": "test_user2",
            "agent_id": first_agent_id,
            "title": "Test Session2 Title",
        },
    )

    client.post(
        "/sessions",
        json={
            "end_user_id": "test_user3",
            "agent_id": second_agent_id,
            "title": "Test Session3 Title",
        },
    )

    response = client.get("/sessions")

    assert response.status_code == status.HTTP_200_OK
    data = response.json()

    assert data

    sessions = data["sessions"]

    assert len(sessions) == 3

    assert sessions[0]["title"] == "Test Session1 Title"
    assert sessions[1]["title"] == "Test Session2 Title"
    assert sessions[2]["title"] == "Test Session3 Title"

    assert sessions[0]["end_user_id"] == "test_user1"
    assert sessions[1]["end_user_id"] == "test_user2"
    assert sessions[2]["end_user_id"] == "test_user3"


def test_that_sessions_can_be_listed_by_agent_id(
    client: TestClient,
) -> None:
    response = client.post(
        "/agents",
        json={"agent_name": "first_test-agent"},
    )
    first_agent_id = AgentId(response.json()["agent_id"])

    response = client.post(
        "/agents",
        json={"agent_name": "second_test-agent"},
    )
    second_agent_id = AgentId(response.json()["agent_id"])
Repository: emcie-co/parlant
File: server/src/emcie/server/core/sessions.py (Python)

Code before:
from __future__ import annotations
from abc import ABC, abstractmethod
import asyncio
from dataclasses import dataclass
from datetime import datetime, timezone
from typing import Any, Literal, Mapping, NewType, Optional, Sequence, TypedDict

from emcie.server.async_utils import Timeout
from emcie.server.core import common
from emcie.server.core.common import ItemNotFoundError, JSONSerializable, UniqueId
from emcie.server.base_models import DefaultBaseModel
from emcie.server.core.agents import AgentId
from emcie.server.core.end_users import EndUserId
from emcie.server.core.persistence.common import NoMatchingDocumentsError
from emcie.server.core.persistence.document_database import DocumentDatabase

SessionId = NewType("SessionId", str)
EventId = NewType("EventId", str)


EventSource = Literal["client", "server"]


@dataclass(frozen=True)
class Event:
    MESSAGE_KIND = "<message>"
    TOOL_KIND = "<tool>"

    id: EventId
    source: EventSource
    kind: str
    creation_utc: datetime
    offset: int
    data: JSONSerializable


Review comment (suggestion):
Note that we're importing `emcie.server.core.common` in two different ways. Let's remove the named module import and stick with importing all of the symbols we need directly.

Code after:
from __future__ import annotations
from abc import ABC, abstractmethod
import asyncio
from dataclasses import dataclass
from datetime import datetime, timezone
from typing import Any, Literal, Mapping, NewType, Optional, Sequence, TypedDict

from emcie.server.async_utils import Timeout
from emcie.server.core.common import ItemNotFoundError, JSONSerializable, UniqueId, generate_id
from emcie.server.base_models import DefaultBaseModel
from emcie.server.core.agents import AgentId
from emcie.server.core.end_users import EndUserId
from emcie.server.core.persistence.common import NoMatchingDocumentsError
from emcie.server.core.persistence.document_database import DocumentDatabase

SessionId = NewType("SessionId", str)
EventId = NewType("EventId", str)


EventSource = Literal["client", "server"]


@dataclass(frozen=True)
class Event:
    MESSAGE_KIND = "<message>"
    TOOL_KIND = "<tool>"

    id: EventId
    source: EventSource
    kind: str
    creation_utc: datetime
    offset: int
    data: JSONSerializable
Repository: emcie-co/parlant
File: server/src/emcie/server/api/sessions.py (Python)

Code before:
from pydantic import Field

from emcie.server.async_utils import Timeout
from emcie.server.base_models import DefaultBaseModel
from emcie.server.core.agents import AgentId
from emcie.server.core.end_users import EndUserId
from emcie.server.core.sessions import (
    Event,
    EventId,
    EventSource,
    SessionId,
    SessionListener,
    SessionStore,
)
from emcie.server.mc import MC


class CreateSessionRequest(DefaultBaseModel):
    end_user_id: EndUserId
    agent_id: AgentId
    title: Optional[str] = None


class CreateSessionResponse(DefaultBaseModel):
    session_id: SessionId
    title: Optional[str] = None
    creation_utc: datetime


class CreateMessageRequest(DefaultBaseModel):
    kind: str = Field(Event.MESSAGE_KIND, description=f'Internal (leave as "{Event.MESSAGE_KIND}")')
    content: str


class CreateEventResponse(DefaultBaseModel):
    event_id: EventId
    event_offset: int


class ConsumptionOffsetsDTO(DefaultBaseModel):
    client: int


class SessionDTO(DefaultBaseModel):
    session_id: SessionId
    end_user_id: EndUserId
    creation_utc: datetime
    consumption_offsets: ConsumptionOffsetsDTO
    title: Optional[str] = None



Review comment (suggestion):
Tiny modification: I would place creation_utc above title and below id for two reasons:
Optional fields should be at the bottom.
Consistency—creation_utc is positioned after id in other parts of the code.

Code after:
from pydantic import Field

from emcie.server.async_utils import Timeout
from emcie.server.base_models import DefaultBaseModel
from emcie.server.core.agents import AgentId
from emcie.server.core.end_users import EndUserId
from emcie.server.core.sessions import (
    EventId,
    EventKind,
    EventSource,
    SessionId,
    SessionListener,
    SessionStore,
)
from emcie.server.mc import MC


class CreateSessionRequest(DefaultBaseModel):
    end_user_id: EndUserId
    agent_id: AgentId
    title: Optional[str] = None


class CreateSessionResponse(DefaultBaseModel):
    session_id: SessionId
    creation_utc: datetime
    title: Optional[str] = None


class CreateMessageRequest(DefaultBaseModel):
    kind: EventKind = Field("message", description='Internal (leave as "message")')
    content: str


class CreateEventResponse(DefaultBaseModel):
    event_id: EventId
    event_offset: int


class ConsumptionOffsetsDTO(DefaultBaseModel):
    client: int


class SessionDTO(DefaultBaseModel):
    session_id: SessionId
    end_user_id: EndUserId
    creation_utc: datetime
    consumption_offsets: ConsumptionOffsetsDTO
    title: Optional[str] = None
Repository: emcie-co/parlant
File: src/parlant/api/sessions.py (Python)

Code before:
class SessionCreationParamsDTO(
    DefaultBaseModel,
    json_schema_extra={"example": session_creation_params_example},
):
    """Parameters for creating a new session."""

    agent_id: SessionAgentIdPath
    customer_id: SessionCreationParamsCustomerIdField = None
    title: Optional[SessionTitleField] = None


message_example = "Hello, I need help with my order"


SessionEventCreationParamsMessageField: TypeAlias = Annotated[
    str,
    Field(
        description="Event payload data, format depends on kind",
        examples=[message_example],
    ),
]

SessionEventCreationParamsActionField: TypeAlias = Annotated[
    str,
    Field(
        description="A single action from the AI agent, such as 'the the user Thinking..' or 'inform the user that more information is coming'",
        examples=[message_example],
    ),
]

event_creation_params_example: ExampleJson = {
    "kind": "message",
    "source": "customer",
    "message": message_example,
}


class UtteranceReasonDTO(Enum):
    """Defines the reason for the action"""

    BUY_TIME = "buy_time"
    FOLLOW_UP = "follow_up"


class UtteranceRequestDTO(DefaultBaseModel):
    action: SessionEventCreationParamsActionField
    reason: UtteranceReasonDTO


class EventCreationParamsDTO(
    DefaultBaseModel,

Review comment (suggestion):
Instead of "the the user is Thinking"
`description='A single action that explains what to say; i.e. "Tell the customer that you're thinking and will be right back with an answer."'`

Code after:
class SessionCreationParamsDTO(
    DefaultBaseModel,
    json_schema_extra={"example": session_creation_params_example},
):
    """Parameters for creating a new session."""

    agent_id: SessionAgentIdPath
    customer_id: SessionCreationParamsCustomerIdField = None
    title: Optional[SessionTitleField] = None


message_example = "Hello, I need help with my order"


SessionEventCreationParamsMessageField: TypeAlias = Annotated[
    str,
    Field(
        description="Event payload data, format depends on kind",
        examples=[message_example],
    ),
]

SessionEventCreationParamsActionField: TypeAlias = Annotated[
    str,
    Field(
        description='A single action that explains what to say; i.e. "Tell the customer that you are thinking and will be right back with an answer."',
        examples=[message_example],
    ),
]

event_creation_params_example: ExampleJson = {
    "kind": "message",
    "source": "customer",
    "message": message_example,
}


class UtteranceReasonDTO(Enum):
    """Defines the reason for the action"""

    BUY_TIME = "buy_time"
    FOLLOW_UP = "follow_up"


class UtteranceRequestDTO(DefaultBaseModel):
    action: SessionEventCreationParamsActionField
    reason: UtteranceReasonDTO


class EventCreationParamsDTO(
    DefaultBaseModel,
Repository: emcie-co/parlant
File: src/parlant/bin/client.py (Python)

Code before:
    )
    @click.option(
        "--check/--no-check",
        type=bool,
        show_default=True,
        default=True,
        help="Check for contradictions with existing guidelines",
    )
    @click.option(
        "--connect/--no-connect",
        type=bool,
        show_default=True,
        default=True,
        help="Connect this guideline with causually related guidelines",
    )
    @click.pass_context
    def guideline_update(
        ctx: click.Context,
        agent_id: str,
        id: str,
        condition: str,
        action: str,
        check: bool,
        connect: bool,
    ) -> None:
        assert condition or action

        agent_id = agent_id if agent_id else Interface.get_default_agent(ctx)
        assert agent_id

        Interface.update_guideline(
            ctx=ctx,
            agent_id=agent_id,
            condition=condition,
            action=action,
            guideline_id=id,
            check=check,
            index=connect,
        )

    @guideline.command("delete", help="Delete a guideline")
    @click.option(
        "--agent-id",
        type=str,
        help="Agent ID",
        metavar="ID",
        required=False,
    )
    @click.option("--id", type=str, metavar="ID", help="Guideline ID", required=True)
    @click.pass_context
    def guideline_delete(

Review comment (suggestion):
This isn't a good way to do it. If someone runs it with neither `--action` or `--condition`, instead of getting a clear error message that at least one is required, they will get an assertion error.

Code after:
    )
    @click.option(
        "--check/--no-check",
        type=bool,
        show_default=True,
        default=True,
        help="Check for contradictions with existing guidelines",
    )
    @click.option(
        "--connect/--no-connect",
        type=bool,
        show_default=True,
        default=True,
        help="Connect this guideline with causually related guidelines",
    )
    @click.pass_context
    def guideline_update(
        ctx: click.Context,
        agent_id: str,
        id: str,
        condition: str,
        action: str,
        check: bool,
        connect: bool,
    ) -> None:
        if not (condition or action):
            Interface.write_error("At least one of --condition or --action must be specified")
            raise FastExit()

        agent_id = agent_id if agent_id else Interface.get_default_agent(ctx)
        assert agent_id

        Interface.update_guideline(
            ctx=ctx,
            agent_id=agent_id,
            condition=condition,
            action=action,
            guideline_id=id,
            check=check,
            index=connect,
        )

    @guideline.command("delete", help="Delete a guideline")
    @click.option(
        "--agent-id",
        type=str,
        help="Agent ID",
        metavar="ID",
        required=False,
    )
    @click.option("--id", type=str, metavar="ID", help="Guideline ID", required=True)
Repository: emcie-co/parlant
File: src/parlant/adapters/nlp/litellm_service.py (Python)

Code before:
# Copyright 2024 Emcie Co Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import annotations
import time
from openai import (
    APIConnectionError,
    APIResponseValidationError,
    APITimeoutError,
    ConflictError,
    InternalServerError,
    RateLimitError,
)
from typing import Any, Mapping
from typing_extensions import override
import json
import jsonfinder  # type: ignore
import os

from pydantic import ValidationError
import tiktoken

import litellm

from parlant.adapters.nlp.common import normalize_json_output
from parlant.adapters.nlp.hugging_face import JinaAIEmbedder
from parlant.core.engines.alpha.prompt_builder import PromptBuilder
from parlant.core.loggers import Logger
from parlant.core.nlp.policies import policy, retry
from parlant.core.nlp.tokenization import EstimatingTokenizer
from parlant.core.nlp.service import NLPService

Review comment (suggestion):
https://docs.litellm.ai/docs/exception_mapping

From the documentation, it looks like litellm inherits from openai, but please change the imports of those error types from the litellm and not openai


Code after:
# Copyright 2024 Emcie Co Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import annotations
import time
from litellm import (
    APIConnectionError,
    APIResponseValidationError,
    Timeout,
    InternalServerError,
    RateLimitError,
)
from typing import Any, Mapping
from typing_extensions import override
import json
import jsonfinder  # type: ignore
import os

from pydantic import ValidationError
import tiktoken

import litellm

from parlant.adapters.nlp.common import normalize_json_output
from parlant.adapters.nlp.hugging_face import JinaAIEmbedder
from parlant.core.engines.alpha.prompt_builder import PromptBuilder
from parlant.core.loggers import Logger
from parlant.core.nlp.policies import policy, retry
from parlant.core.nlp.tokenization import EstimatingTokenizer
from parlant.core.nlp.service import NLPService
from parlant.core.nlp.embedding import Embedder
Repository: emcie-co/parlant
File: src/parlant/adapters/nlp/qwen_service.py (Python)

Code before:
# Copyright 2025 Emcie Co Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import annotations
import time
from openai import (
    APIConnectionError,
    APIResponseValidationError,
    APITimeoutError,
    AsyncClient,
    ConflictError,
    InternalServerError,
    RateLimitError,
)
from typing import Any, Mapping
from typing_extensions import override
import json
import jsonfinder  # type: ignore
import os

from pydantic import ValidationError
import tiktoken

from parlant.adapters.nlp.common import normalize_json_output
from parlant.core.engines.alpha.prompt_builder import PromptBuilder
from parlant.core.loggers import Logger
from parlant.core.nlp.policies import policy, retry

Review comment (suggestion):
Can you please add yourself as a maintainer here?
Like so (right after the license part)
```python
# Maintainer: Full Name <email@address.com>
```

Code after:
# Copyright 2025 Emcie Co Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Maintainer: Ji Qing <jiqing19861123@163.com>

from __future__ import annotations
import time
from openai import (
    APIConnectionError,
    APIResponseValidationError,
    APITimeoutError,
    AsyncClient,
    ConflictError,
    InternalServerError,
    RateLimitError,
)
from typing import Any, Mapping
from typing_extensions import override
import json
import jsonfinder  # type: ignore
import os

from pydantic import ValidationError
import tiktoken

from parlant.adapters.nlp.common import normalize_json_output
from parlant.core.engines.alpha.prompt_builder import PromptBuilder
from parlant.core.loggers import Logger
Repository: emcie-co/parlant
File: src/parlant/adapters/nlp/modelscope_service.py (Python)

Code before:
# Copyright 2025 Emcie Co Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import annotations
import time
from openai import (
    APIConnectionError,
    APIResponseValidationError,
    APITimeoutError,
    AsyncClient,
    ConflictError,
    InternalServerError,
    RateLimitError,
)
from typing import Any, Mapping
from typing_extensions import override
import json
import jsonfinder  # type: ignore
import os

from pydantic import ValidationError
import tiktoken

from parlant.adapters.nlp.common import normalize_json_output
from parlant.adapters.nlp.hugging_face import JinaAIEmbedder
from parlant.core.engines.alpha.prompt_builder import PromptBuilder
from parlant.core.loggers import Logger

Review comment (suggestion):
Hey @yrk111222 can you please add a line right under the license, saying
```python
# Maintainer: Your Name <your@email.com>
```

For example,
```python
# Maintainer: John Smith <john@smith.com>
```

Code after:
# Copyright 2025 Emcie Co Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Maintainer: Rongkun Yan <2493404415@qq.com>

from __future__ import annotations
import time
from openai import (
    APIConnectionError,
    APIResponseValidationError,
    APITimeoutError,
    AsyncClient,
    ConflictError,
    InternalServerError,
    RateLimitError,
)
from typing import Any, Mapping
from typing_extensions import override
import json
import jsonfinder  # type: ignore
import os

from pydantic import ValidationError
import tiktoken

from parlant.adapters.nlp.common import normalize_json_output
from parlant.adapters.nlp.hugging_face import JinaAIEmbedder
from parlant.core.engines.alpha.prompt_builder import PromptBuilder